home *** CD-ROM | disk | FTP | other *** search
Text File | 1996-02-07 | 3.2 MB | 99,905 lines |
Text Truncated. Only the first 1MB is shown below. Download the file for the complete contents.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Resources For Portable C Applications
-
-
- Thomas Plum
-
-
- Dr. Thomas Plum is President of Plum Hall Inc., a training firm that also
- supplies the leading ANSI C validation suite. Dr. Plum serves as Vice Chair of
- ANSI X3J11, the committee that developed the C standard.
-
-
- Let me begin with a bald assertion. Bald Assertion #1: As of 1991, a C source
- file should be either (a) written in non-portable ANSI/ISO Standard C for a
- specific environment (e.g. a device driver), or (b) written in strictly
- portable Standard C suitable for compilation and execution in any environment
- whatsoever. There is no justification for intermediate levels of "somewhat
- portable" code.
- This wasn't what I set out to write about, but it is the inescapable
- conclusion given current resources and projects. In this article, I will
- describe some of the resources that are likely to be important for your work
- on portable C projects. Contact address information is available at the end of
- the article.
-
-
- Books On Portability
-
-
- There are two comprehensive survey books on the line-by-line portability
- coding issues: Portable C, by Rabinowitz and Schaap, and Portability and the C
- Language, by Jaeschke. (See Resources at the end of this article.) Rabinowitz
- and Schaap's book traces its origins back to a seminar that Schaap and I wrote
- for Bell Labs in 1980. At the time Schaap was the first consultant for the
- fledgling Plum Hall Inc. The work has grown into a wealth of detail. It is
- very accurate information. The Jaeschke book is also extremely authoritative.
- A project leader responsible for portability should probably read both books.
- Nevertheless, both books have a point of view that I quarrel with. Besides the
- current C Standard, they cover multiple flavors of "old C" in great detail.
- These dozens (or hundreds?) of pages on "old C problems" are in themselves a
- convincing reason to stick to strict Standard C. The world could use a
- "Standard-only" version of each book. Aside from this, both books are a
- valuable condensation of practical advice from years of project experience.
- Also in the category of collected hints from prior experience is Plum Hall's
- own book, C Programming Guidelines, ("CPG," now in Second Edition for Standard
- C). Presented in the form of a project coding standard, CPG contains
- suggestions on data types, operators, and library functions for portable
- coding. CPG is available in an inexpensive machine-readable format to serve as
- the starting point for each project's own coding standard.
- The fundamental portability resource is, of course, the ANSI C Standard
- itself. It represents seven years of work by hundreds of C experts. The
- corresponding ISO Standard is expected to be official by the end of 1990,
- identical in every way but typographic layout.
-
-
- Portability Testing Software
-
-
- Another condensation of practical experience is the FlexeLint tool from Gimpel
- Software. This is a compile-time syntax analyzer that monitors adherence both
- to Standard C and to a large collection of portability-enhancing coding
- restrictions. For years, Gimpel has had an active customer base suggesting
- (and demanding) practical compile-time tests for portability. FlexeLint
- scrutinizes for portability problems involving the signedness of char,
- variations in character sets, sizes of integers, pointer casts, declarations
- and linkage, called-function vs. calling-function discrepancies, etc.
- I am especially fond of FlexeLint's ability to selectively disable specific
- tests. When it comes to heuristic coding rules, no two projects agree
- completely. I've used FlexeLint on several Plum Hall projects, and it works
- well.
- Similar capabilities are described for the C Portability Verifier from
- Mindcraft, but I haven't been able to test it yet.
- Because the ANSI C Standard is the only official Standard for C, I suggest
- another bald assertion. Bald Assertion #2: Any organization that cares about
- portable C will insist that its procurements require certified
- Standard-conforming compilers.
- Vendors who want to sell compilers to your project can obtain this
- certification from the European Compiler Testing Service (ECTS). This body
- includes the British Standards Institution (UK), AFNOR (France), and IMQ
- (Italy). (The US government standards agency, NIST, plans a different
- approach, probably available late 1991 or early 1992. More on this in another
- article.) ECTS uses the Plum Hall Validation Suite for C as its criterion for
- certification.
- I'll stake my professional reputation on this claim: If your compiler vendor
- truthfully claims to conform to the C Standard, then that vendor's product can
- pass the Plum Hall Suite and get officially certified. You do not have to rely
- upon advertising claims in your procurements. If customers insist upon
- Standard-conforming compilers, vendors will provide them. Several vendors
- already have been fully certified by ECTS. There are dozens of others who
- could get certified if customers demanded it.
- The Standard is central to the subject of C portability because it is the
- "treaty" between compiler vendors and programming projects. It defines exactly
- what capabilities the portable C program can expect from every environment.
-
-
- A Model Implementation
-
-
- The strictness of this treaty makes possible a new form of portability-testing
- tool. It is one that enforces every detailed requirement that the Standard
- imposes upon an application program. Such a tool is the C Model
- Implementation, commissioned and distributed by the British Standards
- Institution and produced by Knowledge Software Ltd.
- Basically, the C Model is an extremely fussy C implementation that
- syntax-checks your application for every compile-time requirement of the
- Standard. It also generates a form of intermediate p-code, runs the p-code
- through an equally fussy linker, then executes the program with diagnosis of
- every runtime "undefined behavior." The net result is that, if your program
- compiles and runs clean under the C Model, and you then compile and run it on
- an environment that conforms faithfully to the Standard, the program will
- compile and run clean there, too.
- Thus, the C Model is an interesting departure from the item-by-item
- accumulation of heuristics that characterized the previous resources
- (including my own C Programming Guidelines). It was produced, test-by-test,
- directly from the Standard itself.
- During this past year, the C Model has found more subtle non-portabilities in
- our Plum Hall Suite than did all the specific hardware environments. It
- demonstrates to its users that strictly portable C takes some discipline to
- produce, but also that it can be done.
-
-
- Shrink-Wrapped C
-
-
- Closely related in concept to the C Model is the Architecture-Neutral
- Distribution Format (ANDF) project of the Open Software Foundation (OSF). The
- goal here is to distribute "shrink-wrapped" applications that can be run
- without modification on any suitable target system. One industry standard
- assumed by ANDF is ANSI/ISO Standard C (as verified by the Plum Hall Suite).
- Another is the OSF/1 version of the UNIX operating system, which in turn is
- specified to meet the IEEE 1003 (POSIX) standard and the X/Open UNIX
- portability standard. OSF is currently conducting a competition (now down to
- four finalists) for a technology that will allow applications to be
- distributed in a "semi-compiled" form. Actual machine-specific code generation
- is postponed until the end-user performs the "install" process.
- Obviously, an application intended for shrink-wrap distribution must be
- totally free from dependencies upon the specific target architecture. Not
- quite so obvious is that, if shrink-wrap ANDF distribution works, the economic
- payoff for strictly portable applications will be greater than ever before. Of
- course, no one can say whether the aggregate UNIX market will ever approach
- the volume of the PC market. Still, many firms find it important to target
- both markets.
-
-
- The Bad News/Opportunity
-
-
- So far, this article has summarized a progression of good news, a historical
- trend of success at overcoming the problems of portably targeting C
- applications at different CPU architectures. These latest developments (a
- standard, a strict test suite, and a strict model implementation) make
- CPU-independent code a practical reality. Now for the bad news, or perhaps I
- should say the "opportunity." (To paraphrase Toshiro Mifune's Samurai in
- Yojimbo, "This looks like my kind of town.") The bad news is, most of the
- difficulties in producing portable applications now lie in the area of support
- environments, not in CPU architectures.
- In the remainder of this article, I will deal with two environment issues: (1)
- International character sets and languages, and (2) Graphical User Interfaces
- (GUIs). There are several other important environment issues besides these
- two. (For a more detailed list, see "Portability -- A No Longer Solved
- Problem," by Feldman and Gentleman.)
- As I mentioned before, the ISO Standard for C will be official by early 1991,
- and identical to ANSI C. But work is progressing on a Normative Addendum that
- will probably add support for multi-byte (e.g., Asian) character sets. When
- implemented, this Addendum will allow applications to manipulate "wide chars"
- of 16 or more bits rather than just one-byte characters. Any locale-specific
- work -- inputting, outputting, classifying, collating, comparing,
- string-handling -- is all handled by the ISO Addendum library, not the
- application itself.
- This standardization work by the ISO C working group builds upon the
- experience of several vendor-specific solutions. Hewlett-Packard has been
- building its Native Language System (NLS) since the mid-1980s. Digital
- Equpiment Corporation and AT&T have both added Kanji (Japanese character)
- support to UNIX. This means that applications can now be designed to be
- compiled into object code and later linked with nationality-specific
- libraries.
-
- Soon, they can be semi-compiled into an ANDF, or even fully compiled and
- linked into executables that dynamically link with nationality-specific
- "shared library" modules at run time. Programs may one day adapt to
- nationalities and character sets that were unknown to the application
- designers and programmers.
- This adds a new dimension, and new complications, to the generality of
- portable software. Many application designers may desire to avoid these
- complications, but the international (i.e., outside USA) marketplace is
- growing too fast to ignore for long.
-
-
- A Portable GUI
-
-
- There is also a connection between "wide char" programming and Graphic User
- Interfaces. A GUI is the only framework that smoothly encompasses both the
- single-byte European character sets and the multi-byte Asian sets. One of the
- major difficulties about designing portable programs for a GUI is that
- environments vary widely: Macintosh, Windows, Presentation Manager, Motif,
- Open Look, etc.
- In my opinion, the most promising approach is the one taken by the Extensible
- Virtual Toolkit (XVT), the brainchild of Marc Rochkind. (He also developed the
- Source Code Control System, or SCCS, in his Bell Labs days.) XVT abstracts the
- common features of all these GUI environments so that your application can be
- written with one common GUI interface. (The interface also supports plain old
- character display terminals, so even anti-GUI mossbacks may find it
- interesting.)
- The generalized model in XVT includes:
- window -- clipping, scrolling, overlaps, coordinates, etc.
- context -- pen thickness, brush pattern, type face, etc.
- menu -- titles, hierarchy, keyboard equivalents, dispatch, etc.
- controls -- scroll bar, default button, check boxes, radio buttons, etc.
- dialog box -- modal or modeless, window specs, list of controls, etc.
- Designing a good portable interface is actually a form of object-oriented
- design. The purpose of the interface is to shelter the application from
- implementation details. This is the important design work for your portable
- application projects, the opportunity lurking in the bad news of the plethora
- of whizbang environments.
-
-
- Resources
-
-
- As promised, here is a summary of the resources I mentioned in this article.
- Henry Rabinowitz and Chaim Schaap, Portable C, PrenticeHall, 1990.
- Rex Jaeschke, Portability and the C Language, Hayden Books, 1988.
- Thomas Plum, C Programming Guidelines, Plum Hall, 1989.
- FlexeLint from Gimpel Software, 3207 Hogarth Lane, Collegeville PA 19426. Tel.
- 215-584-4261. Contact: Jim Gimpel.
- C Portability Verifier from Mindcraft, 410 Cambridge Avenue, Palo Alto CA
- 94306. Tel. 415-323-9000. Contact: Bruce Weiner.
- The ANSI C Standard, ANSI Sales Department, 1430 Broadway, New York NY 10018.
- Tel. 212-642-4900. Ask for X3.159-1989. Orders must be prepaid: $50 plus $6
- for UPS ground shipping. No credit cards.
- The ISO C Standard, ISO Committee SC22/WG14. Contact: P. J. Plauger, Convenor,
- 398 Main Street, Concord MA 01742. Tel. 508-369-8489.
- European Compiler Testing Service, British Standards Institution, POB 375,
- Linford Wood, Brecklands Avenue, Milton Keynes MK14 6L0, United Kingdom. Tel.
- 011-44908-220-908. Contact: Neil Martin.
- The Plum Hall Validation Suite for C, Plum Hall Inc., 1 Spruce Avenue, Cardiff
- NJ 08232. Tel. 609-927-3770. Contact: Thomas Plum.
- The C Model Implementation. See European Compiler Testing Service above.
- Architecture-Neutral Distribution Format (ANDF), OSF: The Open Software
- Foundation, 11 Cambridge Center, First Floor, Cambridge MA 02173. Contact: Sri
- Vasudevan.
- Stuart Feldman and W. Morven Gentleman, "Portability -- A No Longer Solved
- Problem," Computing Systems, Vol. 3, No. 2, Spring 1990, pp. 359-380.
- Jim Brodie, "Extended Multibyte Support," The Journal of C Language
- Translation, Vol. 2, No. 2, September 1990, pp. 133-140.
- David Radoff, "Toward a Global Operating Environment," CommUNIXations, Vol.
- 10, No. 6, August 1990, pp. 15-19.
- The Extensible Virtual Toolkit, XVT from XVT Software Inc., 1800 30th Street,
- POB 17665, Boulder CO 80308. Tel. 303-443-4223. Contact: Marc Rochkind.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Some Thoughts On Portability
-
-
- Jack Purdum
-
-
- Jack Purdum is president of Ecosoft, Inc., and has authored several articles
- and books on C, including The C Programming Guide and a newly released book on
- Quick-C. Dr. Purdum can be reached at 8295 Indy Court, Indianapolis, IN 46214.
-
-
- It seems safe to say that C remains one of the most popular development
- languages available for personal computers. If you ask people who use C why
- they chose it over the alternatives, one reason often given is, "because C is
- a portable language." Perhaps a better statement would be, "C can be a
- portable language." Much has already been written about portability -- more
- than we can cover in one article. The purpose of this article is to discuss
- some portability problems that we've run up against in the past, and to
- discuss what we did (or should have done) to cope with those problems.
-
-
- What Is Portability?
-
-
- In his book Portability and the C Programming Language (Howard Sams), Rex
- Jaeschke defines portability as, "the degree to which a program or other
- software can be moved from one computer system to another."
- That is, how easy is it to successfully recompile the source code in a
- different environment? The "different environment" can take several different
- forms. For example, we might move the source code to:
- 1. a different operating system
- 2. a different CPU
- 3. a different memory model
- 4. a different compiler
- 5. a different combination of the above
- The permutations of the last item in the list are considerable. You might have
- the same operating system on different CPUs (e.g., UNIX on an 80486 or 68030),
- different operating systems on the same CPU (MS-DOS or XENIX on a 80386),
- different compilers on different CPUs and operating systems, and so on.
- To say that C will always fulfill the portability needs for all of the
- possible system combinations that the programmer might face seems a bit much
- to assume. Indeed, it seems unlikely that any non-trivial program can be moved
- between computing environments without some modification. The real issue,
- then, is, "What can I do to make the port less difficult?"
- It should be obvious that the more portable a program is, the less expensive
- it is to move the code to the new environment. The benefits accrue not only in
- getting the program up and running in the new environment, but also in the
- support of the program once the port is complete.
-
-
- Where Am I?
-
-
- In the past thirteen years, our firm has been involved with moving two rather
- large programs (each consisting of several million bytes of source code) to
- different environments. The ports involved different operating systems,
- different CPUs, and even a switch in languages during the port. When we
- switched programming languages, we had the freedom to redesign the program
- with portability in mind. (Alas, we weren't sure where the code would be
- ported to, only that it would likely be ported "somewhere.") With our other
- program, we were more or less locked into the design and had to port the
- program within existing code constraints.
- The degree to which your program is (or will be) portable is influenced by
- where you are in the development cycle. Writing portable code while the
- program is in the design stage is one matter; modifying existing code to be
- portable is quite another. In the remainder of this article, we will examine
- some of the things we did in both situations to improve program portability.
-
-
- Getting Organized
-
-
- If you have the luxury of designing a program with portability in mind, you
- have greater flexibility in writing portable code than if you are locked into
- an existing design. Almost any (nontrivial) program has five major parts, as
- shown in Table 1.
- Program initialization often involves reading a configuration file (containing
- information such as: drive or directory where the data are found, colors,
- fonts, video display information, setting or disabling interrupts, etc.),
- allocating dynamic memory, and a host of other tasks. Program input might
- involve reading the keyboard, a data file, or some other device that supplies
- data to the system. Processing is the manipulation of the data, while output
- sends the processed data to the desired output device. Finally, program
- termination involves any housekeeping tasks required by the program (e.g.,
- closing open files, freeing dynamic memory, updating configuration files, and
- so forth).
- A useful step in the design stage is a "sideways" refinement of the five basic
- elements listed in Table 1. For example, the Output element might be refined
- as shown in Table 2.
- This is a simple process that helps you to identify the type of functions that
- will be needed to display the program output. With a little additional
- thought, the process can help you identify those functions that will probably
- require hardware-dependent (i.e., non-portable) resources. In Table 2, it is
- probably inevitable that the clearscreen() and cursor control functions will
- use non-portable code.
- Having marked the non-portable functions, you should form a source code
- organization plan. Each of the primary program elements in Table 1 should have
- its own subdirectory. For example, the primary directory might be the program
- name or just PROJECT. The subdirectories might be INIT, INPUT, PROCESS,
- OUTPUT, and TERMINAL. In some cases, it may be desirable to have
- subdirectories below each of these directories. For example, under the OUTPUT
- subdirectory, we might have PORT and NOTPORT. This helps to keep the
- non-portable source code separate from the code that can be ported easily.
- Table 3 shows how a typical project might be laid out. (Only the OUTPUT
- directory shows the third layer of disk organization.)
- In the PROJECT directory, all five elements are brought together to form the
- completed application. Each of the five elements will have test code that is
- used to exercise that particular element of the program. If done properly,
- much of the code in these subdirectories will likely end up in their own
- library (LIB) files.
- An alternative is to simply place all of the non-portable source code into a
- single "non-portable" subdirectory. The important thing is for you to identify
- which elements are likely to be non-portable. Once the functions are
- identified and separated as being non-portable, you can look for possible
- alternatives that might allow you to recode them in a more portable fashion.
- At the very least, knowing which functions are non-portable will make you more
- aware of the types of resources you will need in the new environment.
- Using a disk layout similar to that shown in Table 3 assumes that you have a
- means by which you can quickly locate any given function definition. That is,
- you will need a utility program that can read all source files in a
- subdirectory and tell you the file and line number where each function
- definition appears. An example of such a utility can be found in the February
- 1989 issue of Computer Language or in the C Programmer's Toolkit (Que
- Corporation). Something as simple as having an organized plan for the source
- code and being able to locate a given function quickly can significantly
- reduce development time.
- While you are in the process of organizing things, you should limit all I/O
- functions as much as possible. For example, in our statistics package, all
- screen output goes through a single function. Although we didn't anticipate
- it, this is going to prove to be a very smart move when (if?) we move the
- program to Windows. The reason is because printf() is not usable in the
- Windows (or almost any other GUI) environment. Had we used printf() throughout
- the source code, it would take days of editing just to make this one change.
-
-
- Know Your Resources
-
-
- If you know the environment that you are moving to, it will pay to study the
- programming tools that will be used in both environments. For example, when we
- worked in the CP/M environment, the development tools we used supported
- identifiers of up to eight characters. When we switched compilers, the
- compiler still recognized the first eight characters as significant, but the
- linker now only recognized the first six as being significant. We made the
- mistake of assuming that both the compiler and linker recognized the same
- number of significant characters. This little erroneous assumption cost us
- almost two weeks, to reduce the variable names to the shorter length.
- You should also exercise care in making assumptions about other resources and
- features available to you in the two environments. For example, if the
- existing code was written with a K&R compiler and the target machine supports
- an ANSI compiler, certain resources and features may be missing. The K&R
- compiler will support low-level (unbuffered) file I/O while the ANSI compiler
- is only required to supply high-level (buffered) functions as part of the
- standard library. Although present compilers support both types of file I/O,
- it might prove costly to make the assumption in the future.
- Another feature that may be missing (even on UNIX) is that some compilers do
- not support function prototyping. If you find yourself faced with this
- limitation, you will need to create a header file that lists the function
- declarations with prototypes (using, for example, #ifdef ANSI) and without
- (#else) prototypes. The same method can be used to toggle K&R or ANSI coding
- style for function arguments in a function definition. Listing 1 shows an
- example, with the corresponding header file in Listing 2.
- Note that both listings use the K&R style of #ifdef rather than the ANSI #if
- defined preprocessor directive. While the style is not particularly pleasing
- to the eye, it does let you have the advantages of function prototyping in the
- ANSI environment without modifying the source code. And, after all, the goal
- is to support one body of source code for all environments.
- Compiler differences between environments will also affect other coding
- elements. For example, things like structure passing and assignment, certain
- keywords (e.g., const, enum, far, near, void, volatile), and initialization of
- auto arrays can be affected. Even the size of identical data structures may
- vary because one machine may require byte alignment and the other doesn't.
-
- Certain coding techniques should always be used, even if you don't plan to
- port the code to another environment. Hopefully, you already use a number of
- these coding techniques while others might be new to you.
-
-
- Magic Numbers
-
-
- Every C programmer is familiar with the use of #define to avoid using magic
- numbers in a program. For example, define a macro:
- #define TABLESIZE 50
- and then use it as in:
- for (i = 0; i < TABLESIZE; i++) {
- printf("\n%d", number[i]);
- }
- This is a common coding practice. The advantage is that changes to the
- number[] array are easily accounted for by a single change to the symbolic
- constant TABLESIZE regardless of how many times TABLESIZE appears in the
- source code.
- While this approach reduces the amount of editing required in the source file,
- a better solution is possible. For example:
- int tablesize;
- /* main() plus some other code */
- tablesize = sizeof(number) / sizeof(number[0]);
- for (i = 0; i < tablesize; i++) {
- printf("\n%d", number[i]);
- }
- There are several advantages to using this construct instead of the #define.
- First, any changes to the number of elements in the number[] array no longer
- require further editing the source file. (Be honest. Have you ever miscounted
- the number of elements in an array?) The program automatically adjusts to the
- new size of the number[] array. Second, because tablesize is a variable, it
- may be more useful with a source code debugger. Symbolic constants are often
- lost to the debugger.
- Another portability problem arises with macros used as bit masks. While the
- macro
- #define INTEGER_MASK 0x7fff
- works fine on one system, it may fail miserably on a different system. One
- reason is because the size of an integer may vary between machines. A second
- reason might be due to the ordering of the integer in memory. (Is the high or
- low byte stored first?) Related problems may occur with any bitwise operator
- or manipulation of a bit field data item. I'm not sure if there is a totally
- portable means to cope with such problems. You should, however, document such
- items clearly in the source code.
-
-
- Improper Use Of #define
-
-
- In some cases, bad coding practices only show up after a port. One potential
- problem is using a #define when a typedef is more appropriate. We know that
- making the source code more readable is always a good idea. With that in mind,
- we try something like:
- #define INTERGER_POINTER int *
- /* ....some code */
- INTEGER_POINTER table;
- After the preprocessor pass, the definition for table becomes:
- int *table;
- and the code works fine. However, in the process of making the port, you find
- you need a second pointer in the new environment and you change the definition
- statement to:
- INTEGER_POINTER table, delta;
- However, because a macro is a simple textual substitution, the statement
- actually becomes:
- int *table, delta;
- The variable table is defined as a pointer, but delta remains a straight
- integer. The proper solution is a typedef:
- typedef int *INTEGER_POINTER;
- Now the code works the way you intended it to work, even with multiple
- identifiers.
-
-
- String Constants
-
-
- Some time ago we were asked if we wanted to have our statistics package
- translated into a foreign language. While the benefits of the translation
- probably would have been substantial, it required giving out the program
- source code. Had we thought about foreign translations during the design
- stage, we would have altered the way we handled string constants in the
- program.
- We should have written all of the string constants to be defined as an array
- of pointers to char. For example:
- char *message[] = {
- "Select variable name:", /* Message # 0 */
- "All or Subset (A, S):", /* 1 */
- "Printer or File (P, F):" /* 2 */
- /* More in the list */
- "Out of memory" /* N */
- };
- First, this approach uses memory more efficiently because you can have
- multiple occurrences of a constant without using additional memory. (If you
- plan to port to Windows, this method is consistent with placing string
- constants in a resource file.) Second, you can use the "tablesize" approach
- discussed earlier to determine the size of the string table without using a
- macro. Third, anyone who wanted to translate the program need have only a copy
- of the string table, not the code itself. Finally, if a constant does need to
- be changed, a single edit is all that is necessary to change every instance of
- the constant throughout the program.
- As you may know, many foreign countries use different formats for dates, time,
- currency, and similar information. Although our packages don't have these
- particular formatting requirements, they may well apply to your application.
- If that is the case, you should examine the locale.h header file to see if any
- of the symbolic constants defined there can be used to advantage in your code.
- It may help make the port a bit easier.
-
-
-
- Program Input
-
-
- Another fortuitous design decision we made was to use a single function to get
- all input from the keyboard. We felt we had to do this because we needed not
- only to read ASCII keystrokes but also to detect the simultaneous pressing of
- the function and shift keys. Porting the code to an environment like Windows
- won't be easy, but at least all of the keyboard input is isolated to one
- function.
- While we're on the subject, it's been our experience that scanf() is not a
- good function to use for data input. Not only is scanf() a huge function and
- difficult to use properly, the function makes it difficult for your program to
- sense input errors. Further, functions that rely on a terminating newline
- character don't translate well in some environments. In Windows, for example,
- pressing the Enter key is the same as clicking on the OK button.
- Another problem is that the behavior of scanf() may vary among compilers. For
- example, ANSI states that the e and g conversion characters are not case
- sensitive, while System V doesn't specify how these conversion characters are
- viewed. On the other hand, ANSI specifies that the l (for "long") modifier in
- scanf() is case sensitive. For example, "%lf" differs from "%Lf". The latter
- form is used to get a long double. In some environments, however, a long
- double won't even be available in scanf() because that data type is not
- supported (e.g., System V). Little details like these need to be investigated
- if you still wish to use scanf().
- A related problem arises when testing for the end of a user's input from the
- keyboard, regardless of the function used to capture the input. Some compilers
- use the newline character ('\n') to terminate input while others use the
- carriage return character ('\r'). For example, the statement
- if (buff[0] == '\n') {
- /* The user didn't enter anything */
- }
- is often used to see if the user entered anything from the keyboard. The code
- will work fine with one compiler but fail with a different compiler. If your
- code tests for either of these character constants to sense end of input, you
- may want to #define a symbolic constant rather than test for a specific
- character. That is, if your compiler uses the carriage return to terminate
- user input from the keyboard, the code would be more portable if you write
- #define ENDOFINPUT '\r'
- if (buff[0] == ENDOFINPUT) {
- /* The user didn't
- enter anything */
- }
- If data input is coming from a disk file, keep in mind that data sizes may
- vary among environments. For example,
- #define RECORD 50
- fread(buf, RECORD, 1, fpin);
- may not work properly. First, the data types themselves may not be the same
- size. (Does an int require two or four bytes of storage?) Second, the system
- may require the data items to be aligned in some special way. This may mean
- that the data must be padded with extra bytes to fulfill the alignment
- requirements. The moral here is: Don't use a symbolic constant when the sizeof
- operator can accomplish the same task.
-
-
- Numbers
-
-
- We have already mentioned several instances where a difference in the size of
- a data item can have an impact on the program. Most of these considerations
- centered on the storage requirements (such as the size of an int). However,
- the storage requirement for a data item is not the only way such differences
- show up in a program. Clearly, a difference in storage requirements implies
- that the numbers are capable of different numeric ranges. Indeed, even data
- items with the same storage requirements can have a differing ranges of
- values. For example, is the default for a char a signed or unsigned quantity?
- If you are moving to an ANSI compliant compiler, the limits.h header file
- should be helpful in answering such questions. If your present compiler does
- not have the limits.h header file, you might want to consider writing your
- own. Most compilers supply enough information about the data types that
- creating your own limits.h header file is not very difficult. (If you haven't
- done so already, you should examine the symbolic constants defined in this
- header file.)
- Another header file that may prove useful is float.h. It defines almost
- everything you need to know about floating point variables (e.g., number of
- digits of precision, floating point exceptions, exponent limits, etc.). One
- potential problem area for programs that use floating point variables is the
- epsilon factor for a floating point number. (This factor is called DBL_EPSILON
- for type double.) The epsilon factor is the smallest value that can be added
- to 1.0 and satisfy the condition:
- 1.0 + DBL_EPSILON != 1.0
- The value of epsilon can vary widely among compilers. Such wide variation can
- produce bugs that are very difficult to track down. Most C programmers know
- that testing a floating point variable against 0.0 can be dangerous because of
- the epsilon factor. You should check the epsilon factor for both compilers to
- see if they are similar in magnitude. If they are not, you may have to
- incorporate the DBL_EPSILON constant into your code.
-
-
- Odds And Ends
-
-
- Without thinking about it very much, we developed the habit of documenting our
- code, often using nested comments. In other cases, we often leave test code in
- the source file and simply surround it with comment characters. However,
- because we tend to comment the test code as well, we end up with nested
- comments.
- Some compilers do not allow nested comments. It is a real pain to "uncomment"
- nested comments. We have since moved away from commenting out test code and
- now use preprocessor directives to conditionally include the test code in the
- program. For example, we used to comment out the code in the following manner:
- /*
- /* Inspect the values for
- table[] */
- for (i = 0; i < MAXSIZE; i++) {
- printf("table[%d] = %g", i, table[i]);
- }
- */
- This approach results in a nested comment. Still, we feel that the comments
- are often useful and didn't want to simply throw them away. Now, we would
- write the same debug code as:
- #ifdef DEBUG
- /* Inspect the values for
- table[] */
- for (i = 0; i < MAXSIZE; i++) {
- printf("table[%d] = %g", i, table[i]);
- }
- #endif
- If we need to turn on the test code, we can simple insert a #define DEBUG and
- recompile the program to activate the debug code.
- Finally, I'd like to present a brief laundry list of don'ts that can cause
- problems in the middle of a code port.
- Don't use lowercase l as a modifier to a long constant; it looks too much like
- the digit 1. Use the uppercase L, as in
- a = 20L;
- Don't use a long double if you don't really need to. Some compilers don't
- support it yet, plus is can add significantly to the data segment
- requirements. While you're at it, check to see if float arithmetic is
- supported. If your application can use the lower precision, it might prove
- useful.
- Don't assume a pointer is a fixed length, especially in a mixed-model
- environment. Sometimes all data pointers are only two bytes, but function
- pointers are four bytes.
-
- Don't assume wildcard characters are universal across environments. A question
- mark and asterisk may mean nothing on a different system.
- Don't assume a filename is limited to a certain length.
- Don't assume a NULL pointer means all bits are set to 0; it is implementation
- defined. All you can safely assume is that the test for a NULL pointer behaves
- in the normal way (even though the bits may be nonzero).
- Don't use environment variables if you can avoid them. If you can't live
- without them, make sure you mark those functions that use them.
- Don't create your own symbolic constants if ANSI already provides for them
- (e.g., DBL_EPSILON, SEEK_CUR, etc.).
- As a final rule, if you follow most of the suggestions presented here, do take
- the estimated time required for the port, double it, and try to live within
- that time frame. C is not the perfect portable language, but it's way out in
- front of whatever is in second place.
- Table 1 The Five Basic Program Elements
- 1. Initialization
- 2. Input
- 3. Processing
- 4. Output
- 5. Termination
- Table 2 Sideways Refinement of the Output Element
- Table 3 Disk Organization
-
- Listing 1 Function Definitions for K&R and ANSI
- #define ANSI 1
- #include <stdio.h>
- #include <myheader.h>
-
- int main()
- {
- int i;
-
- for (i = 0; i < 10; i++)
- printf("\n%d", func1(i));
- }
-
- #ifdef ANSI
-
- int func1(int i) /* For an ANSI compiler */
-
- #else
-
- int func1(i) /* For a K&R compiler */
- int i;
-
- #endif
- {
- return i * i;
- }
-
-
- Listing 2 A K&R and ANSI Header File
- #ifdef ANSI
-
- int func1(int i);
-
- #else
-
- int func1();
-
- #endif
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Portability Across MS-DOS C Compilers
-
-
- Scott Robert Ladd
-
-
- Scott Robert Ladd is a full-time free-lance writer with over 15 years computer
- programming experience. Scott works mostly in C and C++ when he's not hiking
- and shooting photos with his wife and daughter. He can be contacted at 3652
- County Road 730 Gunnison, CO 81230. (303) 641-4219.
-
-
-
-
- How Portable Is Portable?
-
-
- Programs written in C are often touted as being more portable than those
- written in other languages. To be very portable, a program should use only the
- C language as defined by ANSI. After all, the ANSI standard exists to promote
- portability.
- Most MS-DOS programs, though, use low-level facilities that are not part of
- the ANSI standard. Still, MS-DOS C compilers are remarkably similar. Language
- extensions, such as the near and far keywords, are universally accepted with
- only minor differences in meaning. It's the non-ANSI extensions such as
- directory searching and hardware port I/O that are different from compiler to
- compiler. Experience has taught me that these differences range from the minor
- to the major.
- Is portability among MS-DOS C compilers a mirage, then? No. When I publish C
- programs, they need to be as generic as possible. Presenting a program that
- will, say, compile only with Microsoft's QuickC will generate a torrent of
- letters from Borland and Zortech enthusiasts who want a version for their
- compiler. Necessity is the mother of invention. In this case, my invention was
- a header file that allows me to create programs that automatically adjust to
- the specifics of the C compiler being used. Using portable.h, I can write
- system-level programs that will compile using C compilers from Microsoft,
- Borland, Zortech, and WATCOM.
-
-
- The Header portable.h
-
-
- Listing 1 shows portable.h, a header file containing macros that ease porting
- C programs among MS-DOS compilers. The file consists almost exclusively of
- macro definitions that alias or replace compiler dependencies. There are four
- sections in portable.h, each concerned with a different aspect of portability.
- Every MS-DOS C compiler predefines a macro identifying itself. Microsoft C
- defines the _MSC_VER macros, beginning with v6.0. Microsoft QuickC v2.5 and
- later create the _QC macro. WATCOM C defines the __WATCOMC__ macro. Borland's
- Turbo C and C++ define the __TURBOC__ macro. Zortech C and C++ use the __ZTC__
- macros. You can use these macros in conditional preprocessor statements to
- select compiler-specific characteristics.
- The first macro definition conditionally creates a proper version of the MK_FP
- macro. You use this macro to create a far (32-bit) pointer from 16-bit segment
- and offset values. MK_FP is found in most compiler libraries, but Microsoft's
- C compilers do not define it. If portable.h does not find a MK_FP macro, it
- creates one.
-
-
- Searching Directories
-
-
- The next section of the include file defines macros and types used in
- searching MS-DOS file directories. Working with directory search functions
- under MS-DOS can be frustrating. Each compiler vendor uses unique function and
- structure identifiers. Macros to the rescue! The MS-DOS file data structure
- has the same format for all of the compilers, even if the names of the
- structure and its members are different. I therefore created my own data
- structure, DOSFileData, that can be used in place of the structures peculiar
- to each compiler. The function macros that follow cast a pointer to a
- DOSFileData structure to the specific structure type supported by a specific
- compiler.
- How does a program work with the compiler-specific function names and
- parameter lists? The solution is to define macros that translate between
- compilers. The FIND_FIRST macro hides a compiler's implementation of the
- MS-DOS 0x4E (find first file) function. FIND_NEXT is an alias for the function
- call to the MS-DOS 0x4F (find next file) function.
-
-
- Accessing I/O Ports
-
-
- Aliases also help you perform I/O to ports. Bytes and words can be written to
- and read from the I/O ports in your PC. Access to I/O ports is required for
- video, serial, and other hardware-level programming tasks. The names for port
- I/O functions are consistent across most compilers -- only Borland uses unique
- names. So I created the macros IN_PORT, IN_PORTW, OUT_PORT, and OUT_PORTW as
- aliases for the actual function names.
- Borland's Turbo C and C++ support pseudoregister variables and inline
- interrupt calls. The assignment _AX = 1 directly assigns 1 to the AX processor
- register. The geninterrupt function compiles to an inline INT instruction.
- Obviously, Borland's ability to directly load registers and call system BIOS
- and MS-DOS services is a performance booster. Other MS-DOS C compilers don't
- support these language extensions.
- That would normally preclude the use of pseudoregisters in "portable"
- programs. But again, macros can be used to solve the problem. In the last
- section of portable.h, a macro is defined for each pseudoregister variable
- when a non-Borland compiler is begin used. The pseudoregister is replaced by
- an assignment to a member of a struct REGS named CPURegs. Calls to
- geninterrupt are replaced with calls to int86. I've successfully used these
- macros in several Turbo C-specific programs to allow the code to migrate to
- other C compilers.
-
-
- Conclusion
-
-
- I've found portable.h to be useful both in presenting articles and doing
- consulting work. I can develop a generic program using one compiler and be
- able to use that program with another C compiler. I'm sure you'll find
- extensions that can cover other compiler inconsistencies. Have fun!
-
- Listing 1
- /*======================================================================
- portable.h v1.00 Written by Scott Robert Ladd.
-
- _MSC_VER Microsoft C 6.0 and later
- _QC Microsoft Quick C 2.51 and later
- __TURBOC__ Borland Turbo C and Turbo C++
- __ZTC_ Zortech C and C++
- __WATCOMC__ WATCOM C
- =========================================================================*/
-
-
- /* prevent multiple inclusions of this header file*/
- #if !defined(PORTABLE_H)
- #define PORTABLE_H
-
- /*-----------------------------------------------------------------------
- Pointer-related macros
-
- MK_FP creates a far pointer from segment and offset values
- ------------------------------------------------------------------------*/
-
- #if !defined(MK_FP)
- #define MK_FP(seg,off) ((void far *)(((long)(seg) << 16)(unsigned)(off)))
- #endif
-
- /*-----------------------------------------------------------------------
- Directory search macros and data structures
-
- DOSFileData MS-DOS file data structure
- FIND_FIRST MS-DOS function 0x4E -- find first file matching spec
- FIND_NEXT MS-DOS function 0x4F -- find subsequent files
- -----------------------------------------------------------------------*/
-
- /* make sure the structure is packed on byte boundary */
- #if defined(_MSC_VER) defined(_QC) defined(__WATCOMC__)
- #pragma pack(1)
- #elif defined(__ZTC__)
- #pragma align 1
- #elif defined(__TURBOC__)
- #pragma option -a-
- #endif
-
- /* use this structure in place of compiler-defined file structure */
- typedef struct
- {
- char reserved[21];
- char attrib;
- unsigned time;
- unsigned date;
- long size;
- char name[13];
- }
- DOSFileData;
-
- /* set structure alignment to default */
- #if defined (_MSC_VER) defined(_QC) defined(__WATCOMC__)
- #pragma pack()
- #elif defined(__ZTC__)
- #pragma align
- #elif defined(__TURBOC__)
- #pragma option -a.
- #endif
-
- /* include proper header files and create macros */
- #if defined(_MSC_VER) defined(_QC) defined(__WATCOMC__)
- #include "direct.h"
- #define FIND_FIRST(spec, attr, buf) \
- _dos_findfirst(spec, attr, (struct find_t *)buf) \
- #define FIND_NEXT(buf) _dos_findnex((struct find_t *)buf)
-
- #elif defined(__TURBOC__)
- #include "dir.h"
- #define FIND_FIRST(spec, attr, buf)\
- findfirst(spec, (struct ffblk *)buf, attr)
- #define FIND_NEXT(buf) findnext((struct ffblk *)buf)
- #elif defined(__ZTC__)
- #include "dos.h"
- #define FIND_FIRST(spec, attr, buf) \
- dos_findfirst(spec, attr, (struct DOS_FIND *)buf)
- #define FIND_NEXT(buf) dos_findnext((struct DOS_FIND *)buf)
- #endif
-
- /*-----------------------------------------------------------------------
- I/O Port Macros
-
- IN_PORT read byte from I/O port
- IN_PORTW read word from I/O port
- OUT_PORT write byte to I/O port
- OUT_PORTW write word to I/O port
- -----------------------------------------------------------------------*/
-
- #if defined(__TURBOC__)
- #include "dos.h"
- #define IN_PORT(port) inportb(port)
- #define IN_PORTW(port) inport(port)
- #define OUT_PORT(port,val) noutportb(port,val)
- #define OUT_PORTW(port,val) outport(port,val)
- #else
- #include "conio.h"
-
- #define IN_PORT(port) inp(port)
- #define IN_PORTW(port) inpw(port)
- #define OUT_PORT(port,val) outp(port,val)
- #define OUT_PORTW(port,val) outpw(port,val)
- #endif
-
- /*-----------------------------------------------------------------------
- Borland psuedo register macros
-
- These macros replace references to Borland's psuedo register
- variables and geninterrupt() function with traditional struct
- REGS/int86 references.
- -----------------------------------------------------------------------*/
-
- #if !defined(__TURBOC__)
- #include "dos.h"
-
- struct REGS CPURegs;
-
- #define _AX CPURegs.x.ax
- #define _BX CPURegs.x.bx
- #define _CX CPURegs.x.cx
- #define _DX CPURegs.x.dx
-
- #define _AH CPURegs.h.ah
- #define _AL CPURegs.h.al
- #define _BH CPURegs.h.bh
- #define _BL CPURegs.h.bl
- #define _CH CPURegs.h.ch
-
- #define _CL CPURegs.h.cl
- #define _DH CPURegs.h.dh
- #define _DL CPURegs.h.dl
-
- #define geninterrupt(n) int86(n,&CPURegs,&CPURegs);
- #endif
-
- #endif
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Pennies In Long Double
-
-
- 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 39 Harbor Hill
- Dr., Grosse Pointe Farms, MI 48236.
-
-
-
-
- Coprocessor Problems
-
-
- In a letter to The C Users Journal some time ago, Paul Wexler pointed out some
- confusing results that developed while dealing with dollars and cents on
- numeric coprocessors with extended double precision registers. He showed that
- the Microsoft C v5.1 and Turbo C compilers treated the code:
- double d = 0.99;
- int i = (d *= 100.0);
- as if the second line were written:
- register long double dtemp = (long
- double) d*100.0;
- d = dtemp;
- int i = dtemp;
- with the results d==99.0 and i==98!
- According to K&R or ANSI C, the original code should give the same results as:
- d *= 100.0;
- int i = d;
- So the most popular C compilers conform neither to K&R nor to ANSI! Paul tried
- the same code on some other systems and got correct results.
- I tried it on the Sun 4.0 compiler and found that
- f77 -f68881
- gave the same non-K&R results as Paul had obtained with the Microsoft and
- Turbo compilers, but
- f77 -f68881 -02
- generated a code expansion equivalent to:
- i = d = (long double)d*100.0;
- which gives the correct results.
- The value of 0.99 rounded to the IEEE standard double precision format happens
- to be slightly less than 99/100. (long double)d*100 is less than 99, but if
- rounded again to standard double precision, the result is exactly 99.
-
-
- Normal Surprises In Floating Point
-
-
- Most of us at some time have written something like
- for(x=0.01; x!=l.0; x+=0.01)
- .....
- Because there is no exact binary equivalent to 0.01, the conditional is always
- true. The code might work on a system without extended precision, but is
- likely to hang on 80X87 or 68881 processors. If the condition is changed to
- x<=1.0, the number of iterations will vary by 1 among various systems. Change
- the counter to integral values:
- for(xx=1.; xxd; ++xx) {
- x=xx*0.01;
- ....}
- and it will run portably on all systems with correct floating point
- implementation, no matter what type is given for xx. On many systems, it will
- run faster if the types of x and xx are the same.
-
-
- Try It On Your Own Machine
-
-
- I carried Paul's example a bit further. In every case, the result for i (in
- his example) is either the same as the result j or the result k (when
- compilers take liberties with the standards,) but the k may be less than i
- when varying precisions of floating point arithmetic are used. In single
- precision or PDP-11 style double precision, the value of 0.99 is greater than
- 99/100, and a series of other values must be tried to verify the adherence of
- the compiler to the standard.
- C libraries should be able to convert output to the 17 or 18 digits of
- precision that are required to show the difference between the binary
- approximation and the original decimal value. Any binary floating point number
- may be expressed exactly in decimal, but there is no practical reason or means
- for going beyond the number of digits required to specify a unique binary
- value. Converting the other way, most fractional decimal values have no exact
- equivalent binary fractional value. If properly programmed, the numeric
- coprocessors can perform the conversions reversibly to the extent demanded by
- the IEEE standards. In plain double precision there will be small deviations.
- The inexact converson from decimal to binary is sometimes taken as an argument
- for using decimal arithmetic in applications such as financial calculations.
- However, practical applications of this kind generally have a fixed number of
- fractional decimal places. It should make no difference to the result whether
- binary or decimal arithmetic is used.
- You may wish to see the hexadecimal pattern of the binary floating point
- number. Since the %x conversion is not available for double, you must use
- unions or pointer casts. Converting the value byte by byte avoids some of the
- confusion introduced when the byte storage order is reversed for long or
- double. K&R compilers may have no unsigned char, so the 0xff mask is used to
- compensate. %lx conversion would be expected to work on float if
- sizeof(float)==sizeof(long) and byte orders are not of concern. When you run
- the code, look for the repeating patterns in the hex representation for
- evidence of rounding. When the pattern that is chopped off is greater than
- 0x80..., there should be upward rounding.
-
- If your system does not have extended precision, double rounding (after divide
- and again after subtract) occasionally will give an incorrect rounding. This
- occurred only once in the hex output on my test using Z80 software floating
- point. The same effect reduces the number of different values that printf()
- can produce, making numbers appear to be exact numbers of cents when they
- aren't. This violates the IEEE standard but is normal without extended
- precision.
-
-
- Why Do The Gurus Do This To Us?
-
-
- In the design of the 8087 and 68881 numeric coprocessor families, the number
- of instructions required was reduced by choosing one data type (register long
- double) for the results of all floating point operations. This was a logical
- extension of the traditional C scheme of promoting all float arguments and
- expressions to double. In normal operation, it often gives an extra decimal
- digit of accuracy and avoids most accidents caused by exceeding the range
- provided for double, which was small enough to require special precautions on
- older hardware.
- Since the coprocessors give an incentive in accuracy as well as speed for
- allocation of variables to registers, numerical results may be expected to
- differ on these machines from the values obtained on other architectures. In
- fact, by literal interpretation of the IEEE floating point standard, these
- coprocessors may give incorrect results in double precision. All operations
- are rounded first to 64 bits precision (plus a 15-bit exponent and a sign) and
- then to 53 bits precision (with 11-bit exponent and hidden bit allowing room
- for the sign).
- In effect, the coprocessor rounding threshold varies from about 0.4998 to
- 0.5002 of the unit in the last place (ULP) of a double. Theoretically, this
- could cause the same kind of non-standard behavior, but the possibility is
- remote and over-shadowed by effects of the magnitude of 0.25 ULP such as those
- which we have been discussing. Typical Z80 floating point software routines
- have a rounding threshold which varies from 0.49 to 0.51, and still give
- excellent accuracy.
- You'll hardly notice whether a value that is assigned and then used for
- subsequent calculation is rounded off, unless you push your luck. Meanwhile,
- with the benchmark wars continuing, the compiler writers are looking for every
- chance to maximize the use of register variables. Certain optimizing
- compilers, given Paul's example code, will perform all the calculations at
- compile time, leaving nothing but the printf calls, and no record of the
- sequence of operations used to obtain the constant results.
-
-
- Are The Standards Correct Or Is My Compiler Right?
-
-
- There might be a case for allowing compilers to ignore casts and assignments
- that reduce the width of expressions, but when I write code such as
- int ix;
- double x;
- x -= (ix = ((x=0.0) ? 0.5 : -0.5) + x);
- I expect x to come out such that x += ix would restore the original x. This
- wouldn't work if the double expression were allowed to leapfrog over the
- assignment to ix. When something from the integer family is involved, most of
- us would agree with the standard treatments. The Motorola coprocessors can
- evaluate this expression without reading data back from memory, so there is
- less likelihood of cheating to obtain faster code. On the Intel processors,
- the result that I intended could be produced by
- register long double xtemp = RINT(x);
- /* old Intel FORTRAN function */
- ix = xtemp;
- x - = xtemp;
- but the compiler can't be sure of this. RINT() was Intel's invention. It would
- round a long double to an integral value in accordance with IEEE standards.
- When various precisions of floating point are involved, opinions are not
- unanimous. In the matrix factorization code (3), I assigned the value that was
- to be used in immediately subsequent calculations to a local double variable,
- before assigning it to a float array element, to save the time taken by
- compilers that would either read it back from memory or cast it back to
- double. If I knew that the compiler would generate fast code by leapfrogging
- an assignment, (some do), I wouldn't write it that way.
- Certain compilers will ignore the evaluation implied by an intermediate
- assignment if the assigned value is not actually used later on. I assume that
- this is contrary to the ANSI standard, but I'm sure that others, whose
- opinions count more, will assume differently. This is a gray area, since one
- must be prepared for a compiler to promote a variable to register long double,
- for instance, as long as it does so with reasonable consistency. If the
- variable is never used, by this logic, the compiler could choose to promote it
- to the extent that the implied cast is eliminated. Using the same name for
- another purpose later on doesn't count either, since renaming is an accepted
- technique for optimizing compilers.
- On the other hand, I might want to round an expression off to a precision
- consistent with the numbers from which it came, to avoid problems analogous to
- Paul Wexler's. If the compiler were allowed to pass over assignments and
- casts, it couldn't be done. Besides, it might be difficult to remember if the
- compiler treated double differently from its treatment of int.
- It's easier to write code that guarantees that the rounding will be passed
- over than to prevent the compiler from making a surprise interpretation. The
- standard definitions give more flexibility, control, and self-consistency in
- the language definition.
-
-
- How To Pinch Pennies
-
-
- Paul Wexler feels that C is inadequate to control the operations of numeric
- coprocessors. In particular, he suggests that he would like full control of
- the rounding mode. Naturally, he is dismayed by the occasional theft of 99.44%
- of a penny by the computational troll.
- Several compilers supply system-dependent functions that allow the rounding
- mode to be changed. I suggest that there are valid reasons for their
- unpopularity. First, of course, they don't exist on many compilers and there
- isn't any accepted syntax, so you must accept lack of portability. Secondly,
- the idea of having to break your source code to insert such function calls,
- and take the performance penalties involved in preventing compiler
- optimizations or code pipelining across such calls, is unappealing.
- The need for a way to specify rounding to an integral value has been
- recognized in the definition of many languages and in the IEEE floating point
- standard. For instance, Pascal has the ROUND() function, which was adopted by
- FORTRAN as NINT(). At the same time, FORTRAN adopted the even more important
- rounding without type casting called ANINT(). If it were not for the
- disagreement between the IEEE and the language standards committees over the
- need for rounding to even in the half-way case, the situation would be
- satisfactory in these languages.
- In C, the Pascal ROUND() can be simulated by a macro involving adding or
- subtracting 0.5 before applying (int). Situations remain where it is
- preferable not to use a cast, either for performance or to avoid narrowing the
- range.
- Financial calculations are an example. The way to perform calculations down to
- the cent is to use integral numbers of cents and to round off to the nearest
- cent after each operation that could introduce a fractional value, including
- conversions from dollars to cents. If we always use double precision, numbers
- of cents up to 253-1, or nearly 16 digits (for standard IEEE double
- precision), may be calculated exactly.
- The 8087 appears to have been intended for the integral value floating point
- method of decimal support, as it has an instruction for storing directly from
- register long double to an 18-digit decimal integer. In fact, if the atof()
- function makes use of the 8087, it must convert the input decimal dollar
- figure to cents internally before converting to binary and changing it back to
- dollars by multiplying by 0.01. We would have been better off to convert the
- decimal string to cents ourselves to begin with. Then we could use the
- "inexact" flag to verify that atof() performs an exact conversion.
- The designers of C have been stubborn in not providing a standard floating
- round function. Many processors have one (e.g., the Intel FRNDINT), and the
- inability to have it compiled into the code when it is needed will hurt
- performance. There is no portable way to do it in C. The best that can be done
- is to use a macro with provisions for change to support specific machines.
- Rounding to an integer can be done portably, and conceivable optimizing
- compilers could recognize code such as the ix=x example above and generate
- more efficient code when appropriate.
- Rounding may be performed in IEEE double arithmetic by
- #include <float.h>
- #define ROUNDER 1./DBL_EPSILON
- #define FRNDINT(x) \
- (((x) <ROUNDER) && ((x) >-ROUNDER) \
- ? ((x)>=0.0 ? ((x)+ROUNDER)-
- ROUNDER \
- : ((x)-ROUNDER)+ROUNDER) : (x))
- which normally would be simplified by omitting the cases for absolute value of
- x greater than ROUNDER. Without this precaution, larger numbers are rounded to
- multiples of 2. The constant ROUNDER has the value 252 for standard IEEE
- double precision.
- This code will take over twice as long as the hardware round instruction. It's
- usually faster than the FORTRAN ANINT() functions, which are written to
- overrule the IEEE style rounding of most modern hardware designs. You will
- need to check that your compiler treats the parentheses in accordance with the
- draft ANSI standard. At least one vendor (Convex) has chosen to violate this
- feature of the language standards in search of better benchmark speed. The
- whole macro could be optimized away!
- Lovers of the obscure will notice that a slight rearrangement permits all of
- the comparisons to be performed with integer instructions, if the programmer
- knows the formats actually used for int and double data. A compiler for a
- pipelined or multiple adder machine should produce code such as
- register double temp1,temp2;
- register int tmp1,tmp2;
- temp1 = x+ROUNDER;
- temp2 = x-ROUNDER;
- tmp1 = x==0.0;
- tmp2 = x+ROUNDER;
- temp1 -= ROUNDER;
-
- temp2 += ROUNDER;
- tmp2 &= x-ROUNDER;
- temp1 = tmp1?temp1:temp2;
- temp1 = tmp2?temp1:x;
- which keeps the total elapsed machine time under control and does wonders for
- the megaflops rating of the code. If the number of stages in the pipeline
- times the number of adders is at least four, there is no incentive to try
- integer comparison.
-
-
- Conclusion
-
-
- Considering the amount I have had to say on a seemingly simple topic, one can
- sympathize with C programmers who avoid grappling with floating point. I have
- a little less sympathy with compiler and run time library writers who compound
- the programmer's problems by exceeding the legal limits of optimization or
- just don't think about the reliability issues.
- Normal precautions in the use of floating point may help avoid problems with
- compilers that take shortcuts and deviate from the rounding behavior required
- by the language and hardware standards. Most such situations are recognizable
- and may be taken care of by explicit specification of rounding. Similar
- situations exist where code cannot be expected to be reliably portable without
- such precautions, even among compilers that adhere to the language standard.
- In C, a problem is presented by the lack of a portable way to specify rounding
- without truncating to int.
- References
- Ritchie, Dennis M., The C Programming Language -- Reference Manual, Bell
- Laboratories, Murray Hill, N.J. 1978.
- Harbison, S. P., Steele, G. L. C: A Reference Manual, Second Edition,
- Prentice-Hall 1987.
- Prince, T.C., "Efficient Matrix Coding in C," The C Users Journal, May 1989,
- p. 59.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Using Large Arrays In Turbo C
-
-
- Stuart T. Baird
-
-
- Stuart Baird first learned to program on an IBM 1620 in 1966, going on to
- study computer science at the University of Illinois. Now a General Electric
- employee, he has long since abandoned professional programming for
- administration, but remains an avid recreational programmer at home, using C.
-
-
-
-
- Declaration Of Large Arrays
-
-
- Declaring large arrays within a function such as main() is something
- programmers brought up on other programming languages are used to doing
- without extra thought. For example,
- int a[20000],b[20000];
- is the C equivalent of the DIM or DIMENSION statements that a programmer would
- use in BASIC or FORTRAN. It's a perfectly acceptable declaration in C, and it
- compiles in Turbo C (Borland International's C compiler) without comment. So,
- it comes as a shock when a simple program using that declaration doesn't work.
- Turbo C, of course, is written for the Intel 8086 and related microprocessors,
- which have a segmented memory architecture. These microprocessors normally
- address only one segment, 64K bytes of memory, at a time. The designer of a
- compiler that produces code to execute on such a machine must decide whether
- to insulate the programmer from the idiosyncrasies of the hardware or to cling
- closely to the architecture for greatest control and maximum speed. Given the
- nature of C, the latter is the natural choice. Unfortunately, that choice
- presents unexpected problems to the programmer using large arrays. ("Pointer
- Arithmetic at Memory Segment Boundaries" by Daniel and Nancy Saks, The C Users
- Journal, October 1989, discusses other aspects of this problem.)
- One way Turbo C solves the addressing problem is by using different models of
- the compiler and libraries, depending on the anticipated size of the program.
- Turbo C's documentation disccusses the models. The small model is recommended
- for most programs; it allows 64K bytes of code and 64K bytes of static data.
- This article refers to the small model unless it specifies otherwise. Since I
- discuss only large data, not large code, I will also use the term small memory
- model to refer to any model that limits data to 64K, namely the tiny, small,
- and medium models. The term large memory model will refer to any model that
- lifts that restriction, namely the compact, large, and huge models.
- In the array declarations at the beginning of this article, the Turbo C
- compiler assigns both arrays to the same data segment (of 64K bytes). This
- action is understandable, since in a small memory model a near pointer, only
- two bytes long, is used to address the data. It's impossible for two-byte
- pointers to represent more than 64K different addresses. However, if the
- declared arrays are large enough, they overlap, but the compiler issues no
- warning message. In the example, 40,000 two-byte integers cannot all fit into
- 64K (that is, 65,536) bytes. At some point, one of the arrays wraps around to
- the beginning of the data segment, so some of b's elements "share" memory
- locations with elements of a, which is not good. (Incidentally, you cannot
- assume that b is the array that wraps around, nor can you assume that you have
- a full 64K to work with.) Assignment to the same segment is true even in the
- large memory models, which seems to be a bug.
- The solution must be to use dynamic memory allocation. See Listing 1.
- It's clear that near pointers, which address only within 64K, are
- insufficient, hence the far modifiers. Alas, this doesn't work, either. The
- type of pointer malloc() returns in a small memory model is a near pointer. It
- may gladly return a non-NULL pointer, seemingly indicating memory is
- available, but the address space is still within 64K, and the arrays still
- overlap. Turbo C does remind you of the non-portable pointer assignment (that
- is, the conversion of malloc ()'s near pointer return value to the far pointer
- a) at compile time. You can eliminate the message with a cast, but the problem
- itself remains.
- In the Compact model (one of the large memory models), the previous example
- does work, with or without the far modifiers, which are the default pointer
- type. Other reasons for not using this model will be mentioned later.
- In a small memory model, I must use farmalloc() instead of malloc ():
- int far *a, far *b;
- if ((a = farmalloc(20000L*sizeof(int))) ... etc.
- The library function farmalloc() returns a far pointer, and everything now
- works fine. In particular, I can continue to refer to these allocations as
- arrays, for example
- a[n] = b[n] + 1;
- which of course means the same as
- *(a+n) = *(b+n) + 1;
- A disadvantage of using farmalloc() is that allocations may fail if Turbo C is
- loaded into memory when you run the program. (The complier seems to occupy the
- area of memory where farmalloc() goes looking.) In that case, execution will
- have to be deferred until you exit Turbo C. Standalone execution forfeits
- Turbo C's useful run-time debugging features.
-
-
- Global Data
-
-
- Global data (that is, data elements declared outside of and accessible to all
- functions in a program) seems to be restricted to 64K in all Turbo C's memory
- models - even huge, which, according to the documentation, lifts restrictions
- on static data. Yet it is often useful for a large array to be accessible
- throughout a program.
- Again, the solution is to allocate the large array(s) dynamically in main() as
- above, and keep only pointers to the data in the global area. For example, if
- you need a number of arrays with dimension 8,000, rather than depleting the
- global data area by a multiple of 8,000 bytes per array, store only the
- pointers (at two or four bytes apiece) in the global area. The memory
- allocated for the arrays will be somewhere else. See Listing 2. (The error
- returns from malloc() and farmalloc() are not checked in Listing 2 but should
- be in an actual program.) With 40,000 bytes allocated beforehand, the last
- array cannot possibly fit within the same 64K as the other arrays, so it needs
- to use farmalloc() and the modifier far (or huge).
- Global items, of course, make the programmer honor-bound to use names and
- types consistently in all functions. Unintentional conflicts are difficult to
- catch. Furthermore a function that uses global data can't be lifted out and
- used in another program without rework. A language such as BASIC has no other
- choice, but C does. As an alternative to using global data, you can pass
- pointers as arguments to those functions that need them. Although incurring
- some additional overhead, this method is often regarded as "cleaner" since the
- function is then self-contained.
-
-
- Scope Of Modifiers In Declarations
-
-
- In the declaration
- extern int i,j,k;
- all three variables are understood to be both external and integers. However,
- in a Turbo C declaration
- int far *p,*q,*r;
- only p is a far pointer. q and r (if compiled in a small data model) have the
- default attribute near. If q and r are supposed to be far pointers to int as
- well, you must use
- int far *p, far *q, far *r;
- as you've already done in previous examples, or use a typedef:
- typedef int far * fpi;
- fpi p,q,r;
- This might seem to be a quirk of the non-standard modifiers near, far, and
- huge, but there is a precedent in Standard C. The indirection symbol * also
- binds to the variable. [So do the type qualifiers const and volatile. -pjp] In
- the declaration
- int * p,q,r;
- p is a pointer to int, while q and r are just ints.
- In general, the scope of the elements in a declaration (that is, to how much
- of the complete declaration they apply) does not seem to be well documented in
- C - the documentation, if it exists, is not obvious. I couldn't find this
- topic specifically addressed in K&R, in Standard C, or in Turbo C's
- documentation. It's clear what syntax is legal, but it's not immediately clear
- just what the construction means, until you try it in a program. Lack of
- documentation is unfortunate, but the dialect resulting from having to use
- non-standard features (the modifiers) is the real culprit.
-
-
-
- Arrays Greater Than 64K
-
-
- Allocating an array of greater than 64K bytes using farmalloc() is easy:
- allocate the memory using a long integer expression (for example 100000L),
- assign the return value (the starting address) to a pointer, and use the
- pointer as an array name.
- What kind of pointer? Clearly not a near pointer, which can only address
- within a 64K segment. It is not a far pointer, either. While far pointers are
- capable of addressing anywhere in memory, far pointer arithmetic does not work
- beyond 64K. Suppose you have a large array and pointers to it named a and b,
- respectively:
- char far *a, far *b;
- long count = 100000L;
- a = farmalloc(count);
- (Normally you would check for a NULL return indicating allocation failed, of
- course.) Suppose you try to initialize the array to blanks:
- b = a;
- while (count- -) *b++ = ' ';
- You would find that only the first 65,536 or so characters of array a were
- initialized properly; the remainder would be untouched. That is because when b
- has a value of a+65535 and is then incremented, the next value becomes not
- a+65536 but a+0. In the interest of speed, far pointers are not checked for
- possible overflow, leading to wraparound and erroneous results such as this.
- Since Turbo C's documentation is clear on this point, the arithmetic error
- cannot be considered a bug. Less clear from the documentation is what happens
- if you use subscripting instead
- long i;
- for (i=0L; i<count; i++) a[i] = ' ';
- Unfortunately, this works exactly the same as the pointer version above. The
- address of a[65536] is translated to the pointer a+65536, which (not checking
- for overflow, long arithmetic notwithstanding) becomes just a (or &a[0]);
- a[65537] becomes a[1], etc. All the elements beyond 65535 are therefore
- inaccessible. The same problem surfaces earlier with larger data types: a
- two-byte int array declared as far can't go beyond element 32767, for example.
- The numbers above have been quoted as though exactly 65,536 bytes are
- available. In practice, Turbo C seems to require a few bytes of overhead. The
- offset portion of the allocated addresses are 8, not 0. This implies that the
- wraparound takes place earlier: at element 65527 or 32763, for instance.
- The solution is to declare such pointers as huge rather than far. Both far and
- huge pointers are capable of representing the same range of addresses, and
- both take up the same amount of space (four bytes). The difference is that
- huge pointers are normalized after every arithmetic operation. Normalization
- yields correct results, though at some cost in speed. (far and huge pointers
- and normalization are discussed in Turbo C's documentation and need not be
- covered in detail here.)
-
-
- Piecemeal Allocation
-
-
- Why allocate everything at once? In some applications, it makes sense to
- allocate memory only as it is required. Perhaps the program can be designed to
- be flexible. Accumulate data and fill as much memory as is available. Then if
- eventually an allocation request fails, process what is there and continue on.
- One might wish to use this approach for a word processor or sort utility, for
- example.
- There's a significant difference between piecemeal allocation and allocating
- everything at once, however. The single large allocation
- a = malloc(10000);
- is guaranteed to give the address of a block of 10,000 bytes of memory in a
- row (unless there is an error return or wraparound, of course). That block can
- be treated as an array. Alas, there is no such guarantee with individual small
- allocations. Examine the following piece of code, which allocates memory one
- byte at a time and stores individual characters as they are read:
- char *a,*b;
- int count=0;
- a = b = malloc(1);
- while ((*b = getchar()) != EOF) {
- b = malloc(1);
- count++;
- }
- So far, this ought to work (again assuming no error returns). Characters are
- read and stored. This subsequent code, however, would not work:
- for (i = 0; i<count; i++)
- putchar (a [i ] );
- This code would not work because it is a mistake to assume that the addresses
- that successive allocations assigned to b are contiguous. You could not
- blithely refer to the second character obtained by getchar() as a[1], unless
- somehow you had ascertained that the second value that malloc() assigns to b
- were indeed larger than the first by exactly sizeof(*a). In Turbo C, using
- either malloc() or farmalloc(), it definitely isn't.
- If what you really want is one large array, piecemeal allocation is not
- practical.
-
-
- Pointers To Pointers
-
-
- Suppose you have an array a declared as a huge pointer:
- char huge *a;
- a = farmalloc(100000L);
- Perhaps it contains a number of strings with variable lengths. If you wanted
- to refer to one of those strings, you would need another huge pointer to do
- so:
- char huge *aptr;
- You might want to refer to all of them as a set, for example to maintain an
- index:
- char huge *aindex[NSTRINGS];
- Suppose that the first three elements of aindex refer to strings which begin
- at a, a+47000L, and a+93333L. With 100,000 elements allocated, and huge
- pointers, these are all valid addresses. Rather than make this example
- completely dry and numerical, let's say that the second of these strings is
- Skipping rivulet and fountain.
- Now suppose you want to use a pointer to refer to the various elements of
- aindex, perhaps just to step through the index or (in a typical application)
- to sort it. What kind of pointer do you need? The array aindex could be quite
- small, perhaps even just the three elements previously defined. So in that
- case, an ordinary near pointer would suffice. The task at hand is to declare
- that pointer. While a compiler ought to be able to parse such a declaration,
- it would be confusing to the ordinary reader, since it would contain both the
- huge and near modifiers. (In a small memory model, only the huge modifier,
- even though the resulting pointer type is near.) I wouldn't even attempt it
- without using a typedef:
- typedef char huge * hugecp;
- hugecp aindex[NSTRINGS];
- hugecp near *pindex;
- In a small memory model, the near modifier is unnecessary, but why not make
- things explicit for clarity?
- On the other hand, you might need aindex to contain very many elements. Since
- a huge pointer is four bytes long, any value of NSTRINGS over 16,383 (or so)
- would mean you have to declare pindex as
-
- hugecp huge *pindex;
- In fact, declaring aindex as an array would no longer work, since it would be
- larger than 64K. You would need to allocate it dynamically:
- hugecp huge *aindex;
- aindex = farmalloc((long)
- NSTRINGS*sizeof(hugecp));
- (Again, better check for an error return in actual practice.) If you used this
- method of allocating aindex even if its size were small, you would still need
- to declare pindex huge (or far) just to allow it to point to the correct data
- segment, since the far heap where aindex is allocated is somewhere different
- from where automatic variables are allocated.
- Let us now look at the resulting pointer pindex. Suppose
- pindex = &aindex[1];
- What is *pindex? It is the contents of element number 1 of aindex, which is
- the address a+47000L. Regardless of whether pindex is near, far, or huge,
- *pindex is a huge pointer, referring to the string "Skipping rivulet and
- fountain." How about **pindex? It is the contents of address a+47000L, the
- character 'S'. The value of *(*pindex + 1) would be 'k', etc. The type of
- **pindex is char. Given all this, you'd think we could just declare pindex as
- follows:
- char **pindex;
- but that would (presumably) make both *pindex and pindex the default type of
- pointer, namely near in the small data models. That wouldn't do, in our
- example. If you declared
- char huge **pindex;
- then (presumably) *pindex would be a huge pointer, but pindex would still be
- near - or is it the other way around? This wouldn't do, either. Besides being
- unclear, it would probably not have the effect of making both of them huge.
- In summary, declarations for pointers to pointers need extra care when large
- arrays are involved. Though we haven't discussed it, arrays with double
- subscripts require the same care.
-
-
- Mixed Pointers
-
-
- Turbo C allows you to mix near, far, and huge pointers in any of the memory
- models, with some restrictions noted in the documentation. Mixed pointers are
- perhaps slightly difficult to keep untangled in one's own code and functions,
- but with care it can be done. Using the standard library with mixed pointers,
- however, is much more of a problem.
- Some standard functions, such as printf(), have built-in ways to deal with
- large pointers in a small data model. For example, to print as an address the
- value of an ordinary (near) pointer in a small data model, you use the format
- specification %p, but it's possible to print the value of a far (or huge)
- pointer using %Fp. The options are limited, however, and it's easy to make
- mistakes. One such mistake is supplying pointers of one size when printf() is
- expecting pointers of a different size. There is generally no compile-time
- warning, and the mismatch between format specification and parameter list
- gives unpredictable results.
- Turbo C calls in separate libraries suitable for the different memory models,
- so that in a large data model, such as compact, the version of strcpy()
- contained in the library accepts far pointers, which are the default in that
- model. For most programs, which do not need to mix pointers, this is a boon.
- The programmer does not need to worry about a different set of standard
- functions with unique names, let alone write new ones. Most standard functions
- cannot cope with non-default pointers, however. If you need a capability
- provided by a standard function, you are forced to program around the
- limitation, such as by writing a new function or performing an intermediate
- conversion.
- In the huge memory model, far pointers (not huge pointers) are still the
- default. The standard library in that case also uses far pointers. A function
- that accepts far pointers will also accept huge pointers. The format is the
- same, and the value of a huge pointer is just one of many far pointer values
- that refer to a particular address. However, the limitations of far pointers
- previously discussed also apply to the internal workings of such functions.
- The standard library functions may not work properly with large arrays. The
- function qsort(), for example, which works so beautifully most of the time,
- fails for arrays larger than 64K. You can provide it with huge pointers as
- arguments, but you can't make it use huge pointers internally.
- Incidentally, there seems to be a small bug in printf() in the huge model.
- Even though far pointers are the default, an obscure linker error message
- complains if you try to use %p to print a far pointer's value. Use %Fp and the
- message goes away.
- In other words, be wary of mixing near, far, and huge pointer types,
- especially when using the standard library. You may end up having to write
- functions for your particular pointer needs.
-
-
- Summary
-
-
- Although my experience with C on an 8086-style microprocessor is limited to
- Borland's Turbo C, it seems reasonable to assume that other C compilers for
- segmented memory micros will have similar limitations when large arrays must
- be used. Arrays whose sizes are large enough to cross a segment boundary
- require much more understanding and planning on the part of the programmer.
- Those of us who need to use large arrays in C look forward to the time when
- improved software or hardware in personal computers makes the size of an array
- transparent!
-
- Listing 1
- int far *a, far *b;
- if ((a = malloc(20000*sizeof(int))) == NULL) {
- printf("cannot allocate a\n");
- exit(1);
- }
- if ((b = malloc ... etc.
-
-
- Listing 2
- char *a;
- int *b,*c;
- double far *d;
- main()
- {
- a = malloc(8000*sizeof(char));
- b = malloc(8000*sizeof(int));
- c = malloc(8000*sizeof(int));
- d = farmalloc(8000L*sizeof(double));
- fct(); ...
- }
- fct()
- {
- a[3] = ...; /* global arrays accessible */
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Multi-Threaded C Functions
-
-
- AJM Beddow
-
-
- Mr. Beddow is an electrical engineer with 15 years experience in electronic
- and computer system design. His current project involves real-time distributed
- databases. You may contact him at P.O. Box 36 Wantage, Oxon, OX12 8LL, United
- Kingdom.
-
-
- OS/2 incorporates lightweight threads to implement parallel control with
- efficient use of system resources. You can construct functions that create the
- context for multi-threaded execution, control the execution of the threads,
- and remove the multi-thread context when the thread functions terminate. You
- can do this with the standard C library under any operating system.
- The example of multi-threading uses an explicit function call to change thread
- context to the next thread of execution. This switch is normally made when an
- I/O resource is blocked and a thread is required to wait on the resource. The
- disadvantage of this non-preemptive thread switching is that you must add
- explicit thread switches within a thread's computation intensive parts to
- prevent its hogging the CPU. The advantage of non-preemptive thread switching
- is that reentrant calls to DOS can't run. The result is generally more
- efficient than the pre-emptive context switching for a non-reentrant operating
- system.
- All thread functions are loaded as part of the one program. You create the
- threads by reserving stack space for each thread, creating a dispatch table,
- and dispatching the first thread. If all threads terminate, then the stack
- context space is recovered and single thread execution continues where it left
- off. The thread creation function is non-reentrant. It cannot be called from
- inside a spawned thread.
-
-
- How It Is Done
-
-
- To accomplish multi-threading in C, use the standard functions longjmp() and
- setjmp(). The setjmp() function takes an environment pointer and creates an
- environment for the current machine state, returning the value zero when it
- has done so. A longjmp() with the environment pointer as its parameter causes
- the program thread to return once more from a non-zero return value. The stack
- pointer and frame pointer are adjusted to the values saved in the environment
- when the setjmp() was originally called. This is the basis of the mechanism
- for switching thread context.
- Listing 1 is an example of setjmp() and longjmp() functions for small model C
- programs. To return to a thread with its stack context intact, you must
- reserve a stack area for each thread. You achieve this by recursively calling
- a thread creation function with a large automatic array, and modifying the
- stack pointer in a thread environment to point to the top of the array.
- Listing 2 shows my multi-threading functions. The function thread() takes a
- null terminated list of function pointers and copies the pointers into the
- threaddata structure array. Thread() then sets up a root environment and calls
- threadrun(). Threadrun() calls itself recursively creating an environment for
- each thread on the stack and storing a pointer to the environment in the
- threaddata structure array. It also modifies each stack pointer in the
- environment to point to the top of its automatic array stack-space.
- Figure 1 shows the resulting stack arrangement of individual thread
- environments and stacks. Threadrun() then makes a longjmp() call to the first
- thread and returns to the setjmp point in threadrun() with a return value of
- -1. The first thread function pointer is then dispatched and the first thread
- runs. You make subsequent thread switches by calling threadswitch(), which
- sets the thread state flag to waiting, saves the thread environment, and calls
- longjmp() with the next thread environment to dispatch the next thread. When a
- function terminates, it returns to threadrun() which sets the thread state to
- terminated and calls threadswitch() to run the next available thread. When
- threadrun() finds all threads have terminated, it calls longjmp() with a
- pointer to the root environment, the thread stack spaces are recovered, and
- single (normal) thread execution resumes.
- Note that threadswitch() calls have no effect in single thread execution and
- can be embedded into functions with no ill effects to single thread execution.
- The macro SPINDX defines the offset in words of the stack pointer parameter in
- the environment space. Use DEBUG on a setjmp() call to determine the offset
- for your own C compiler.
-
-
- Example And Uses
-
-
- In Listing 3, the main() function calls thread() with a list of three function
- pointers. Each function has two printf() function calls with a thread switch
- between them. Each function executes in turn to print one statement at a time.
- Aside from this example, multi-threading allows execution sharing between
- program function components to be separated from the function control flow. A
- state-sequencing function for example, may process multiple character streams
- using a state-sequencer thread for each stream and thread switching when a
- input stream is blocked. In real- time applications this is a low overhead
- method of resource sharing between non-time critical tasks.
- Figure 1
-
- Listing 1
- ; Unless declared otherwise all
- ; values are type short
- ; setjmp(array pointer) : set
- ; environment of 3 integers,
- ; returns 0
- ; array -> bp of calling function
- ; sp current
- ; address of setjmp return
- PUBLIC setjmp
- set jmp PROC NEAR
- ;
- mov bx,sp
- mov ax,ss:[bx]
- mov bx,ss:[bx+2]
- mov ss:[bx],bp
- mov ss:[bx+2],sp
- mov ss:[bx+4],ax
- xor ax,ax
- ret
- ;
- setjmp ENDP
- ; longjmp(array pointer, constant) :
- ; non local goto, returns constant
- PUBLIC longjmp
- longjmp PROC NEAR
- ;
-
- xor ax,ax
- mov bp,sp
- mov bx,[bp+2]
- mov ax,[bp+4]
- or ax,ax
- jne L1
- inc ax
- L1:
- mov bp,ss:[bx]
- mov sp,ss:[bx+2]
- mov cx,ss:[bx+4]
- mov bx,sp
- mov ss:[bx],cx
- ret
- ;
- longjmp ENDP
-
-
- Listing 2
- /* ************************** */
- /* Multi-threading functions */
-
- #include <setjmp.h>
- #define TSTACKSIZE 200 /* Stack space reseved in each thread in words */
- #define MAXTHREAD 20 /* Max No of concurrent threads */
- #define SPINDX 1 /* Index of stack pointer into env */
- #define NULL 0
- static struct threaddat {
- int *env ; /* Environment pointer */
- int (*tfp)() ; /* Thread function pointer */
- int state ; /* 0 Waiting 1 running -1 terminated */
- } threaddata[MAXTHREAD] ;
- /* Globals for thread() */
- static jmp buf envroot ; /* root environment */
- static int threadnum = -1 ; /* Number of threads */
- static int threadindx ; /* Thread indexer */
- /* Takes a NULL terminated list of function pointers, sets up
- the global parameters above and the root environment and then
- calls threadrun() to define and run the function threads. */
- thread(fplist)
- int (*fplist)() ;
- {
- int (**fplptr)() ;
- void threadrun() ;
- if (threadnum == -1)
- { fplptr = &fplist ;
- threadnum = 0 ;
- while (*fplptr != NULL)
- threaddata(threadnum++).tfp = *fplptr++ ;
- threadindx = 0 ;
- if (!setjmp(envroot))
- threadrun() ; /* define thread environments recursivly */
- /* then run them to extinction */
- }
- }
- /* recusivly defines thread environments then runs in round robin */
- void threadrun()
- {
- jmp_buf envthread ;
-
- int stackspace[TSTACKSIZE) ;
- if (threadindx < threadnum)
- { if (setjmp(envthread))
- { /* Will arrive here by longjmp for function dispatch */
- (*threaddata[threadindx].tfp)() ; /* Start thread */
- threaddata[threadindx].state = -1 ; /* terminated */
- threadswitch() ;
- printf("Multi-threading error - terminating ) ;
- exit(1) ;
- }
- else
- {
- /* Global pointer to thread env */
- threaddata[threadindx].env = envthread ;
- /* Thread state to waiting to run */
- threaddata[threadindx].state = 0 ;
- /* Modify env value for stack with a little margin for luck */
- envthread[SPINDX] = (int) &stackspace[TSTACKSIZE-5] ;
- threadrun(++threadindx) ; /* Recusive call for next thread env */
- }
- }
- else /* Run threads in round robin */
- {
- /* Start the ball rolling with thread 1 */
- threadindx = 0 ;
- longjmp(threaddata[threadindx].env, -1) ;
- }
- }
- /* Thread switcher : Will switch to next available function thread in list.
- If function threads have all terminated will terminate the threads
- environment and return to the root enviroment */
- threadswitch()
- { int i ;
- if (threadnum != -1)
-
- {
- if (threaddata[threadindx].state != -1)
- threaddata[threadindx] .state = 0 ;
- if (!setjmp(threaddata[threadindx].env))
- {
- for (i=1; i <= threadnum; i++)
- { if (++threadindx >= threadnum)
- threadindx = 0 ;
- if (!threaddata[threadindx].state)
- { threaddata[threadindx].state = 1 ;
- longjmp(threaddata[threadindx].env, -1) ;
- }
- }
- threadnum = -1 ;
- longjmp(envroot, -1 ) ;
- }
- }
- }
-
-
- Listing 3
- /* ************************************************* */
- /* Demonstration of technique for multi-threading C */
-
-
- #define NULL 0
- main()
- {
- extern case0(), case1(), case2() ;
- thread( case0, case1, case2, NULL) ;
- }
- int case0()
- {
- printf("** First thread part 1 ** ) ;
- threadswitch() ;
- printf("** First thread part 2 ** ) ;
- }
- int case1()
- {
- printf("** Second thread part 1 ** ) ;
- threadswitch() ;
- printf("** Second thread part 2 ** ) ;
- }
- int case2()
- {
- printf("** Third thread part 1 ** ) ;
- threadswitch() ;
- printf("** Third thread part 2 ** ) ;
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Some Tips For QuickSort
-
-
- Joe Celko
-
-
- Joe Celko is a college teacher and consultant based in Los Angeles, as well as
- a member of the ANSI X3H2 Database Standards Committee. He wrote a biweekly
- column on software design in Information Systems News for four years and has
- taught structured methods in industry and colleges.
-
-
- Mark Nelson's article on QuickSort (CUJ, August 1990)[1] examined tuning an
- algorithm to fit a particular need. In this article, I will supply you with
- more tricks to improve QuickSoft performance.
- The choice of the pivot (also called the key value) is the most important
- factor in the performance of each partition in the sort. The statistical
- distribution of the data to be sorted is the most important factor in picking
- the pivot.
- Another helpful point not mentioned in previous QuickSort articles is that the
- pivot value does not have to be an actual value in the array being sorted. The
- code changes a little bit, but the result is that everything in the left
- partition is less than the pivot, and everything in the right partition is
- greater. Now you can construct a pivot from a sample of the values in the
- partition without actually looking for an occurrence of it.
- If you have a uniform probability distribution in the data, as you would
- sorting on a unique key, then using the first array element of each partition
- (the first naive sort given in Nelson's article) will work as well or better
- than any fancier method.
- Using the middle position of the partition as the pivot also works well in
- practice since many files already have some order to them from previous
- sorting. The code for the middle point, pivot = (first + last)/2, will be
- optimized to a shift operation instead of a divide by most C compilers.
- If you have a normal probability distribution (the so-called bell curve) with
- any skew (tilt to one side) in the data, then the "median of three" algorithm
- for picking a pivot will work much better. Using three values reduces
- comparisons by 14 percent, but more than three does not seem to give much
- improvement.
- QuickSort is a non-stable sort. A stable sort preserves the original order in
- the file for records with duplicate sort keys. Stable sorts are nice in one
- sense because they can be applied one after the other. If I need to sort a
- file by counties for one report, and by counties within states for another
- report, and I use a stable sort, then I can run the first report followed by a
- stable sort on the states alone for the second report.
- Unfortunately, stable sorts tend to be slower than non-stable sorts. On the
- bright side, you can modify the QuickSort to be a stable sort. You do this by
- scanning from left to right, partitioning the records into two separate files
- in their original order and then appending them together in order. The
- internal sorting phase is also done with a stable sort.
- Recursion is not the enemy. William Hutchison of Exton, PA collects versions
- of QuickSort written in C to use for compiler and hardware testing on a
- standard set of data files. He reports in private correspondence that he found
- that recursive versions of QuickSort often ran faster than non-recursive
- versions of the same variation. Modern stack hardware makes an important
- difference.
- Using a different sort for partitions smaller than some size M is a good
- trick, since many sorts run faster than QuickSort for small arrays. An even
- better way is to ignore the small subfiles during the first phase of the sort,
- then go back through the entire files and sort it with a procedure tuned for
- handling M or fewer records. You save the overhead of invoking the small
- partition sort over and over while the QuickSort is running. The best size for
- M is 9 or 10, but anything between 6 and 15 will work about as well, according
- to analysis [2].
- Insertion sort is a good choice, but you can also use the Bose-Nelson sort.
- This algorithm does not sort directly, but it generates the minimum number of
- swap pairs for a fixed size array. A swap pair is a pair of array elements
- that are to be compared and then swapped if they are out of order [3].
- Another trick is to see if a partition is already sorted and to then leave it
- alone. If the file is already in near sorted order, this can save a lot of
- time. You do this by adding Boolean flags to the left and right pointer
- control loops, which are set when an out-of-place element is scanned.
- If equal keys are present, then Nelson's first program will keep moving the
- high and low pointers so that all the duplicates are left in their proper
- position in the middle. Surprisingly, analysis shows that it is better to stop
- when the pointers are equal to the pivot element's position.
- Wainwright [2] measured the performance of several modifications of QuickSort
- on the same sets of test and got some interesting results. The best
- performance seems to be from what he called Bsort -- a QuickSort with a check
- for sorted order in the partitions.
- References
- [1] Nelson, Mark, "Writing Your Own QuickSort," The C Users Journal, Aug 1990,
- pp 63-73.
- [2] Wainwright, Roger L., "A Class Of Sorting Algorithms Based On QuickSort,"
- Communications of the ACM, Apr 1985, Vol 28, No 4, pp 396-402.
- [3] Celko, Joe, "Bose-Nelson Sort," Dr. Dobb's Journal, September 1985.
- Hoare, C. A. R., "Algorithm 64: QuickSort," Communications of the ACM, April
- 1961, Vol 4, No 7, p 321.
- Sedgewick, Robert, Implementing QuickSort Programs, Communications of the ACM,
- Oct 1978, Vol 21, No 10 pp 847-857.
- Sedgewick, Robert, "QuickSort With Equal Keys," SIAM Journal of Computing, Jun
- 1977, Vol 6, No 2, pp 240-257.
- vanEmdan. M.N., "Algorithm 402: Increasing The Efficiency Of QuickSort,"
- Communications of the ACM, Nov 1970, Vol 13, No 11, pp 693-694.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Balanced Binary Trees In C++
-
-
- Bob Jarvis
-
-
- This article is not available in electronic form.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- The iRMX Family Of Operating Systems
-
-
- Richard Carver
-
-
- Richard Carver is a software engineer with six years experience programming in
- C on iRMX systems. He received his B.S. in computer science from Akron
- University (Akron, Ohio). He has worked on projects involving financial
- transaction processing, security systems and medical diagnostics equipment. He
- may be reached on CompuServe (71061,1754) at the Real-Time Forum (GO REALTIME)
- or by phone at (708) 249-0633.
-
-
- iRMX is a "layered real-time object-oriented multi-user multitasking
- interrupted-driven priority-based preemptive operating system." iRMX I, iRMX
- II and iRMX III are a family of operating systems designed for the Intel 80x86
- family of microprocessors. iRMX I uses "Real Mode" addressing, which accesses
- up to 1 megabyte of system memory. iRMX II uses the "Protected Virtual Address
- Mode" (PVAM) of the 80286 and 80386/486 processors, which permits addressing
- up to 16 megabytes. In addition, the protected mode supports segment boundary
- protection, stack-overflow detection, invalid segment detection and segment
- access rights (read, write, or execute). With iRMX III, the 32-bit addressing
- of the 80386/486 allows accessing up to 4 gigabytes using PVAM.
- The iRMX operating systems are supported for three major industry-standard
- 80x86-based bus architectures: Intel's MULTIBUS I & II and IBM's PC/AT
- architecture.
- In almost all cases, applications written for a lower member of the OS family
- can be easily ported to a higher member (e.g., iRMX I to iRMX II). In fact, by
- using the features of the 80386/486 processor for supporting 16-bit and 32-bit
- code, almost all iRMX II applications (executable code) can be run directly
- under iRMX III.
-
-
- Basics Of The iRMX Nucleus Layer
-
-
- At the heart of the iRMX OS is the Nucleus Layer. This layer of the operating
- system supports scheduling, controls access to system resources, provides
- communication between processes and processors, and enables the system to
- respond to external events. It provides the basic "objects" for all other
- layers (Basic I/O, Extended I/O, Application Loader, and Human Interface) and
- applications. The objects provided by the Nucleus include: Tasks, Jobs,
- Segments, Mailboxes, Semaphores, Regions, Extension Objects, and Composite
- Objects. These objects may be created, destroyed and manipulated by programs.
- When an object is created, a token for the object is returned. This object
- token is used for all references to the object.
- Within the iRMX OS, work is performed by tasks. The tasks operate within the
- environment provided by the job. This includes the tasks, objects used by the
- tasks, a directory for tasks to catalog objects and a memory pool. Every job
- has at least one task to refer to as the "initial" task. This task may create
- other tasks to accomplish the work of the program. Tasks may also create new
- jobs (child jobs) with their own environments. Creating new jobs results in a
- Job Tree with parent/child connections. Tasks creating other tasks do not
- result in parent/child relationships. Tasks within a single job are part of
- the job as a whole.
- Tasks may communicate with each other using the iRMX "Exchange Objects." The
- three types of exchange objects are mailboxes, semaphores, and regions.
- Mailboxes can be either data mailboxes, used for sending and receiving data,
- or object mailboxes, which are used for sending and receiving objects.
- Mailboxes are created and deleted using the rqcreatemailbox and
- rqdeletemailbox system calls. Data mailboxes are used with the rqsenddata and
- rqreceivedata system calls to transfer data (up to 128 bytes) from one task to
- another. Data is physically copied from the sender to the receiver using the
- mailbox as a transfer buffer. Object mailboxes are used with the rqsendmessage
- and rqreceivemessage system call to pass object tokens between tasks. The
- object token may be for any valid iRMX object (e.g., tasks, jobs, segments,
- mailboxes, semaphores, regions).
- Semaphores are the "keepers of units." A unit is an abstract object that can
- be sent to or received from a semaphore. Semaphores are often used for
- controlling access to shared system resources (e.g., data, devices) or for
- synchronizing tasks. iRMX semaphores can also be used as "counting"
- semaphores, which means that they are capable of holding more than a single
- unit. Semaphores are supported through the rqcreatesemaphore,
- rqdeletesemaphore, rqsendunits, and rqreceiveunits system calls.
- Regions are special forms of semaphores. Tasks may receive or release control
- of regions. In this manner, regions work like "single unit" semaphores.
- However, when a task receives control of a region, its priority is increased
- to allow the task to run until it releases the region. Also, the task cannot
- be suspended or deleted until the region is released. Because of this, regions
- are only used in situations where semaphores do not provide enough control.
- Regions are supported through the rqcreateregion, rqdeleteregion,
- rqsendcontrol, and rqreceivecontrol system calls.
-
-
- Sharing Through Tasks
-
-
- You can accomplish access to shared resources, such as data or devices, by
- using semaphores. However, sometimes it makes more sense to implement resource
- control through tasks. For a given resource, such as a terminal, a task is
- written to provide the services of this resource. The task accepts requests
- from other tasks for operations to be performed, such as printing a message to
- the display.
- The example program consists of five tasks. The first task is the initial task
- which it responsible for creating all other tasks and program termination. The
- clock and crt tasks act as resource managers for the clock and crt,
- respectively. The count and timer tasks are the resource consumers.
- This program uses only semaphores and object mailboxes for communication.
- Regions are rarely used and are specifically not recommended for use in
- operator-executed programs. Data mailboxes are not used because they only
- provide for one-way communications. Typically, two-way communication, such as
- that provided by rqsendmessage and rqreceivemessage with object mailboxes,
- provides task synchronization.
-
-
- The Initial Task
-
-
- The initial task is the main() program. It creates many of the objects used
- during the program and catalogs these objects so that the other tasks may use
- them. This task then creates one task at a time, each time waiting for a task
- to indicate that it has completed initialization by sending a unit, using
- rqsendunit, to the initialization semaphore (init_sem). When the last task,
- timer_task(), signals its completion, the shutdown process begins. Each
- remaining task is sent a unit to its respective shutdown semaphores. When all
- tasks indicate they have shutdown, all objects are cleaned up (uncatalogued
- and deleted) and the program is terminated.
-
-
- The Clock Task
-
-
- The clock_task() provides access to the hardware clock and implements the
- software timers. All timers are maintained using the single hardware interrupt
- from the clock board. This task also maintains the display time/date by
- communicating with the crt_task().
- The clock_task has been set-up to service an interrupt. It became an interrupt
- task when it called the rqsetinterrupt system call. This call specifies the
- interrupt to be serviced and the address of an interrupt handler. The handler
- is not the task, it is a function that receives control when the interrupt
- occurs. The interrupt handler passes control to the interrupt task by using
- rqsignal interrupt. The interrupt task is waiting to receive control through
- rqetimedinterrupt. The main difference between the handler and the task is
- that all interrupts are disabled when the handler is in control. Only the
- interrupt being serviced is disabled when the task in control. Also, interrupt
- handlers are restricted to interrupt-related system calls. Since this
- implementation needs to access semaphores and mailboxes, it must use an
- interrupt task.
- When an interrupt occurs, the task first requests access to the shared
- time/date data segment. It then checks to see if the hardware clock needs to
- be changed (supported but not used in example). If no change is required, the
- current time/date is advanced by one second. Next, all software timers, if
- any, are advanced by one second. If a timer's timeout value is reached, a unit
- is sent to the timer semaphore and the timer restarts. Finally, the time/date
- is formatted and sent to the crt_task() for display. This task communication
- is synchronous, but the send and receive do not occur one after the other. A
- display message is not sent unless the response to the last request has been
- received. This prevents the task from being delayed during interrupt
- processing and prevents a build up of request messages when the crt_task() is
- busy (which could occur since the crt_task() has a lower priority). The minor
- side effect is that the time/date display may skip a second when then
- crt_task() is busy.
- When the task receives the shutdown signal, it waits for the outstanding
- display response and calls rqresetinterrupt to disable itself as an interrupt
- processing task. It will then suspend itself to await deletion.
-
-
- The Crt Task
-
-
- The crt_task() provides shared access to the video display. This is a basic
- implementation, providing only a means of printing a string at a specific row
- and column of the display. The task receives request messages that indicate a
- message to be displayed and the location to display the message. After
- displaying the message, the task determines if a response is expected. If the
- calling task indicated a "response mailbox" in the rqsendmessage call, then it
- will be expecting a response. This facility is used for synchronizing the task
- communication. The response will be the original message segment. If the task
- did not expect a response, the "response mailbox" value will be the null token
- (NUL_TKN). In this case, this task will free the request message segment.
- When the task receives the shutdown signal, it will go into an infinite loop
- of receiving requests and returning response messages (if a response is
- required). Since many tasks send a request and then wait for the response, you
- don't want tasks getting stuck waiting for responses. This could happen if the
- crt_task() quits responding after a shutdown signal. Requests messages will
- pile up in the request mailbox and will never be processed. Minimum processing
- to send the response is needed.
-
-
- The Count Task
-
-
-
- The count_task() continuously counts from 0-65535 and displays the value in
- the middle of the screen by communicating with the crt_task(). This task's
- purpose is to help demonstrate how multiple tasks can use the crt_task() to
- safely share the video display.
- Upon receiving a unit at its shutdown semaphore, the task suspends itself to
- await deletion.
-
-
- The Timer Task
-
-
- The timer_task() uses a software timer, maintained by the clock_task(), to
- determine when the program should be terminated. The task creates a one-second
- timer and waits for the timer to be signalled 20 times (20 seconds). The timer
- is actually a semaphore that has been set up so that the clock_task() sends a
- unit to the semaphore every second. The timer_task() calls rqreceiveunits to
- wait for 20 units to be sent to the semaphore. (A single unit from a 20-second
- timer could also be used.) When 20 seconds have elapsed, the task sends a unit
- to the initialization semaphore (init_sem). In this case, receiving a unit at
- init_sem not only indicates that the task has initialized but that it is also
- time to terminate the program. Since it initiates the shutdown signal process,
- it suspends itself.
-
-
- Summary
-
-
- The clock_task() is a fairly complete task. There isn't much more useful
- functionality that would be added to service a hardware clock. On the other
- hand, the crt_task() is only the core of what could be a sophisticated display
- servicing task supporting many features (e.g., video attributes, hotkeys,
- windows).
- Remember, the example program is just that -- an example of task
- communications. It is by no means the only way to implement and organize tasks
- within an application program. Because multitasking operating systems are
- usually very complex, you should use good design and modular programming
- techniques. But don't let this scare you, once you become familiar with a
- multitasking operating system, such as iRMX, you'll wonder know how you ever
- got along without it.
- iRMX C Code For 80x86 Processors
- The ANSI C compiler for the iRMX operating system provides many features for
- working with the 80x86 family of processors. The selector data type is used to
- designate tokens that basically correspond to the addresses of object
- descriptors. The selector type could be replaced with the unsigned int type.
- The example uses the selector type because it is used by all system call
- prototypes in RMXC. H. The #pragma interrupt directive tells the compiler to
- generate the proper prologue and epilogue (e.g., IRET) for an interrupt
- function. The are also many built-in functions for dealing directly with the
- 80x86 processor. In this example, inbyte and outbyte functions are used for
- reading and writing I/O ports.
-
- Listing 1 (clockint.c)
- /*0*********************************************************
- * Richard Carver July 1990
- *
- * This program demonstrates interprocess communication
- * between several tasks using the iRMX II Operating
- * System.
- *
- * Two tasks are used for resource control. The
- * clock_task() controls use of the hardware clock and the
- * crt_task() controls use of the display screen.
- *
- * The count_task() competes with the clock_task() for
- * use of the crt_task() services. The timer_task() uses
- * the services of the clock_task() for a software timer.
- *
- * The initial task, main(), starts every thing up and,
- * when the timer_task() finishes, shuts everything down.
- ***********************************************************/
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <rmxc.h> /* iRMX OS calls & structures */
- #include <rmxerr.h> /* iRMX status codes (defines) */
- #include <delay.h> /* Hardcoded delay loop macros */
-
- #define ERROR (0xFFFF) /* Error Condition */
-
- #define FALSE (0)
- #define TRUE (!FALSE)
-
- #define MAX_CLOCK_RETRIES (4)
-
- /* The NULL token (similar to NULL pointer) */
-
- #define NUL_TKN ((selector)0)
-
- /* Options for rqgettasktokens() */
-
- #define THIS_TASK (0x00)
-
- #define THIS_JOB (0x01)
- #define PARAM_OBJ (0x02)
- #define ROOT_JOB (0x03)
-
- /* Options for rqcreatemailbox() */
-
- #define FIFO_OBJ_MBX (0x0000)
- #define PRIORITY_OBJ_HBX (0x0001)
- #define FIFO_DATA_MBX (0x0020)
- #define PRIORITY_DATA_MBX (0x0021)
-
- /* Options for rqcreatesemaphore() */
-
- #define FIFO_SEM (0x0000)
- #define PRIORITY_SEM (0x0001)
-
- #define SEM_INIT_0 (0)
- #define SEM_MAX_1 (1)
-
- /* Common wait times for iRMX System Calls */
-
- #define NO_WAIT (0x0000)
- #define WAIT_FOREVER (0xFFFF)
-
- /* Options for rqcreatetask() */
-
- #define NO_FLOAT (0x0000)
- #define DEFAULT_STACK_SIZE ((unsigned int)2048)
-
- /* Task Priorities */
-
- #define CLOCK_TASK_PRIORITY ((unsigned char)48)
- #define TIMER_TASK_PRIORITY ((unsigned char)150)
- #define CRT_TASK_PRIORITY ((unsigned char)200)
- #define COUNT_TASK_PRIORITY ((unsigned char)200)
-
- /* Defines For Hardware Clock */
- /* Clock Interrupt Level */
-
- #define CLOCK_INT_LEVEL (0x26)
-
- /* Clock Ports */
-
- #define ADR_PORT ((unsigned short)0xA060)
- #define DAT_PORT ((unsigned short)0xA062)
- #define CNT_PORT ((unsigned short)0xA064)
- #define PIO_PORT ((unsigned short)0xA066)
-
- /* Clock Port I/O Operations */
-
- #define PIO_READ ((unsigned char)0x82)
- #define PIO_WRITE ((unsigned char)0x80)
-
- /* Clock Registers */
-
- #define SEC01_REG (0x00)
- #define SEC10_REG (0x01)
- #define MIN01_REG (0x02)
- #define MIN10_REG (0x03)
-
- #define HRS01_REG (0x04)
- #define HRS10_REG (0x05)
- #define DAT01_REG (0x07)
- #define DAT10_REG (0x08)
- #define MON01_REG (0x09)
- #define MON10_REG (0x0A)
- #define YRS01_REG (0x0B)
- #define YRS10_REG (0x0C)
-
- /* Clock Control Lines */
-
- #define CNT_RST ((unsigned char)0x00)
- #define INT_ENB ((unsigned char)0x20)
- #define HOLD_HIGH ((unsigned char)0x10)
- #define HOLD_READ ((unsigned char)0x30)
- #define HOLD_WRITE ((unsigned char)0x50)
-
- /* Defines for months */
-
- #define JANUARY (1)
- #define FEBRUARY (2)
- #define MARCH (3)
- #define DECEMBER (12)
-
- /* Time/Date and Timers Data Structure */
-
- #define MAX_TIMERS (10)
-
- struct SYS_TIME_STR
- {
- unsigned char change_flg;
- unsigned char second;
- unsigned char minute;
- unsigned char hour;
- unsigned char date;
- unsigned char month;
- unsigned char year;
-
- struct TIMER_STR
- {
- unsigned char in_use;
- unsigned int count;
- unsigned int timeout;
- selector sem_tkn;
- }timer[MAX_TIMERS];
- };
-
- /* Request Message format for the Display Task */
-
- struct CRT_REQ_STR
- {
- unsigned char *msg_ptr;
- unsigned int row;
- unsigned int col;
- };
-
- /* Object Catalog Names */
-
- const unsigned char
-
- clock_sem_name[] = "\011CLOCK_SEM",
- init_sem_name[] = "\010INIT_SEM";
-
- const unsigned char
- time_seg_name[] = "\010TIME_SEG";
-
- const unsigned char
- crt_req_mbx_name[] = "\007CRT_MBX";
-
- const unsigned char
- crt_shtdwn_sem_name[] = "\012CRT_SHTDWN",
- clock_shtdwn_sem_name[] = "\014CLOCK_SHTDWN",
- count_shtdwn_sem_name[] = "\014COUNT_SHTDWN";
-
- /* Days Per Month */
-
- const unsigned char
- days_in_month[12] =
- {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
-
- /* Global System Time/Date Structure */
-
- selector sys_time_seg;
- struct SYS_TIME_STR *sys_time;
-
- /* Task declarations */
-
- void clock_task(void);
- void crt_task(void);
- void timer_task(void);
- void count_task(void);
-
- /* Task objects (global for debugging purposes) */
-
- selector clock_tsk;
- selector crt_tsk;
- selector timer_tsk;
- selector count_tsk;
-
- /*1*********************************************************
- * Task: main() (the initial task)
- *
- * Summary: Starts it all up and shuts it all down.
- *
- * Caveats: None
- ***********************************************************/
-
- void main(void)
- {
- selector current_job; /* current job */
- selector init_sem; /* initilaization sem */
- selector clock_sem; /* time/date shared memory*/
- selector clock_shtdwn_sem; /* clock_task() shutdown */
- selector count_shtdwn_sem; /* count_task() shutdown */
- selector crt_shtdwn_sem; /* crt_task() shutdown */
- selector sys_time_seg; /* time/date data segment */
-
- unsigned int status; /* iRMX condition code */
-
-
- struct EXCEPTIONSTRUCT exceptioninfo;
-
- /* Disable current task's exception handler */
-
- rqgetexceptionhandler(&exceptioninfo, &status);
- exceptioninfo.exceptionmode = 0;
- rqsetexceptionhandler(&exceptioninfo, &status);
- /* Get Token For Current Job */
-
- current_job = rqgettasktokens(THIS_JOB, &status);
-
- /* Create and catalog the semaphore used for */
- /* accessing the shared clock data. */
-
- clock_sem = rqcreatesemaphore(SEM_INIT_0,
- SEM_MAX_1, PRIORITY_SEM, &status);
- rqcatalogobject(current_job,
- clock_sem, &clock_sem_name, &status);
-
- /* Create and catalog the semaphore used for */
- /* synchronizing tasks during initialization */
- /* and shutdown. */
-
- init_sem = rqcreatesemaphore(SEM_INIT_0,
- 99, FIFO_SEM, &status);
- rqcatalogobject(current_job,
- init_sem, &init_sem_name, &status);
-
- /* Create and catalog the segment of memory */
- /* used to hold the shared time/date data. */
-
- sys_time_seg = rqcreatesegment(
- sizeof(struct SYS_TIME_STR), &status);
- rqcatalogobject(current_job,
- sys_time_seg, &time_seg_name, &status);
-
- /* Create and catalog the semaphores used to */
- /* signal task shutdown. */
-
- clock_shtdwn_sem = rqcreatesemaphore(SEM_INIT_0,
- SEM_MAX_1, FIFO_SEM, &status);
- rqcatalogobject(current_job,
- clock_shtdwn_sem, &clock_shtdwn_sem_name, &status);
-
- crt_shtdwn_sem = rqcreatesemaphore(SEM_INIT_0,
- SEM_MAX_1, FIFO_SEM, &status);
- rqcatalogobject(current_job,
- crt_shtdwn_sem, &crt_shtdwn_sem_name, &status);
-
- count_shtdwn_sem = rqcreatesemaphore(SEM_INIT_0,
- SEM_MAX_1, FIFO_SEM, &status);
- rqcatalogobject(current_job,
- count_shtdwn_sem, &count_shtdwn_sem_name, &status);
-
- /* Create the crt_task(). Wait for the task to */
- /* indicate it has completed initialization. */
-
- crt_tsk = rqcreatetask(CRT_TASK_PRIORITY, &crt_task,
- NUL_TKN, NULL, DEFAULT_STACK_SIZE, NO_FLOAT, &status);
-
-
- rqreceiveunits(init_sem, 1, WAIT_FOREVER, &status);
-
- /* Create the clock_task(). Wait for the task */
- /* to indicate it has completed initialization. */
-
- clock_tsk = rqcreatetask(CLOCK_TASK_PRIORITY, &clock_task,
- NUL_TKN, NULL, DEFAULT_STACK_SIZE, NO_FLOAT, &status);
-
- rqreceiveunits(init_sem, 1, WAIT_FOREVER, &status);
-
- /* Create the count_task(). Wait for the task */
- /* to indicate it has completed initialization. */
-
- count_tsk = rqcreatetask(COUNT_TASK_PRIORITY, &count_task,
- NUL_TKN, NULL, DEFAULT_STACK_SIZE, NO_FLOAT, &status);
-
- rqreceiveunits(init_sem, 1, WAIT_FOREVER, &status);
-
- /* Create the timer_task(). Wait for the task */
- /* to indicate it has completed initialization. */
- /* This also signals the shutdown process. */
- timer_tsk = rqcreatetask(TIMER_TASK_PRIORITY, &timer_task,
- NUL_TKN, NULL, DEFAULT_STACK_SIZE, NO_FLOAT, &status);
-
- rqreceiveunits(init_sem, 1, WAIT_FOREVER, &status);
-
- /* Begin shutdown by first disabling any further */
- /* interrupts from the hardware clock. This task */
- /* suspends itself for 2/100th of a second to */
- /* ensure interrupt is disabled. */
-
- rqdisable(CLOCK_INT_LEVEL, &status);
- rqsleep(2, &status);
-
- /* Send shutdown signals to all tasks and wait */
- /* for all tasks to complete shutdown. */
-
- rqsendunits(count_shtdwn_sem, 1, &status);
- rqsendunits(crt_shtdwn_sem, 1, &status);
- rqsendunits(clock_shtdwn_sem, 1, &status);
-
- rqreceiveunits(init_sem, 3, WAIT_FOREVER, &status);
-
- /* Cleanup before terminating. This includes */
- /* deleting tasks and uncataloging/deleteing */
- /* all objects created. */
-
- rqdeletetask(crt_tsk, &status);
- rqdeletetask(clock_tsk, &status);
- rqdeletetask(count_tsk, &status);
- rqdeletetask(timer_tsk, &status);
-
- rquncatalogobject(current_job,
- &clock_sem_name, &status);
- rquncatalogobject(current_job,
- &init_sem_name, &status);
- rquncatalogobject(current_job,
- &time_seg_name, &status);
-
- rquncatalogobject(current_job,
- &crt_req_mbx_name, &status);
-
- rquncatalogobject(current_job,
- &clock_shtdwn_sem_name, &status);
- rquncatalogobject(current_job,
- &count_shtdwn_sem_name, &status);
- rquncatalogobject(current_job,
- &crt_shtdwn_sem_name, &status);
-
- rqdeletesemaphore(clock_sem, &status);
- rqdeletesemaphore(init_sem, &status);
-
- rqdeletesemaphore(clock_shtdwn_sem, &status);
- rqdeletesemaphore(count_shtdwn_sem, &status);
- rqdeletesemaphore(crt_shtdwn_sem, &status);
-
- rqdeletesegment(sys_time_seg, &status);
-
- cursor_on();
- printf("\nAll Done!\n");
-
- exit(0);
- }
-
- /*1*********************************************************
- * Function: start_timer
- *
- * Summary: Create and start a timer.
- *
- * Invocation: timer_sem = start_timer(timeout);
- *
- * Inputs: timeout (unsigned int) - timer interval
- * in seconds
- *
- * Outputs: timer_sem (selector) - the semaphore that will
- * receive a unit at each timer interval
- *
- * Caveats: Accesses the time/date data segment that is
- * shared with the clock_task().
- ***********************************************************/
-
- selector start_timer(unsigned int timeout)
- {
- unsigned int status;
- unsigned int indx;
- unsigned int done;
- unsigned int no_more_timers;
-
- selector timer_sem;
- selector current_job;
- selector clock_sem;
- selector sys_time_seg;
-
- struct SYS_TIME_STR *sys_time;
-
- indx = 0;
- done = FALSE;
- no_more_timers = TRUE;
-
- timer_sem = NUL_TKN;
-
- /* Get objects for access semaphore and */
- /* time/date data segment */
-
- current_job = rqgettasktokens(THIS_JOB, &status);
-
- clock_sem = rqlookupobject(current_job,
- &clock_sem_name, NO_WAIT, &status);
- sys_time_seg = rqlookupobject(current_job,
- &time_seg_name, NO_WAIT, &status);
-
- sys_time = buildptr(sys_time_seg, 0);
-
- /* Access time/date memory */
-
- rqreceiveunits(clock_sem, 1, WAIT_FOREVER, &status);
-
- /* Find an empty timer slot */
-
- while (!done)
- {
- if (!sys_time->timer[indx].in_use)
- {
- done = TRUE;
- no_more_timers = FALSE;
- }
- else
- {
- indx++;
-
- if (indx == MAX_TIMERS)
- {
- done = TRUE;
- }
- }
- }
-
- /* Establish a timer */
-
- if (no_more_timers == FALSE)
- {
- if (timeout ! = 0)
- {
- timer_sem = rqcreatesemaphore(SEM_INIT_0,
- 999, FIFO_SEM, &status);
- sys_time->timer[indx].in_use = TRUE;
- sys_time->timer[indx].count = 0;
- sys_time->timer[indx].timeout = timeout;
- sys_time->timer[indx].sem_tkn = timer_sem;
- }
- }
- /* Release time/date memory */
-
- rqsendunits(clock_sem, 1, &status);
-
- /* Return timer semaphore to caller */
-
- return(timer_sem);
-
- }
-
- /*1*********************************************************
- * Function: stop_timer
- *
- * Summary: Delete a timer.
- *
- * Invocation: timer_sem = start_timer(timeout);
- *
- * Inputs: timeout (unsigned int) - timer interval
- * in seconds
- *
- * Outputs: timer_sem (selector) - the semaphore that will
- * receive a unit at each timer interval
- *
- * Caveats: Accesses the time/date data segment that is
- * shared with the clock_task().
- ***********************************************************/
-
- unsigned int stop_timer(selector timer_sem)
- {
- unsigned int status;
- unsigned int indx;
- unsigned int done;
- unsigned int invalid_timer;
-
- selector current_job;
- selector clock_sem;
- selector sys_time_seg;
-
- struct SYS_TIME_STR *sys_time;
-
- indx = 0;
- done = FALSE;
- invalid_timer = TRUE;
-
- /* Get objects for access semaphore and */
- /* time/date data segment */
-
- current_job = rqgettasktokens(THIS_JOB, &status);
-
- clock_sem = rqlookupobject(current_job,
- &clock_sem_name, NO_WAIT, &status);
- sys_time_seg = rqlookupobject(current_job,
- &time_seg_name, NO_WAIT, &status);
-
- sys_time = buildptr(sys_time_seg, 0);
-
- /* Access time/date memory. */
-
- rqreceiveunits(clock_sem, 1, WAIT_FOREVER, &status);
-
- /* Locate and remove this timer */
-
- while (!done)
- {
- if (sys_time->timer[indx].in_use &&
- (sys_time->timer[indx].sem_tkn == timer_sem))
- {
-
- rqdeletesemaphore(timer_sem, &status);
- sys_time->timer[indx].in_use = FALSE;
- sys_time->timer[indx].sem_tkn = NUL_TKN;
- sys_time->timer[indx].count = 0;
- sys_time->timer[indx].timeout = 0;
- done = TRUE;
- invalid_timer = FALSE;
- }
- else
- {
- indx++;
-
- if (indx == MAX_TIMERS)
- {
- done = TRUE;
- }
- }
- }
-
- /* Release time/date memory */
-
- rqsendunits(clock_sem, 1, &status);
-
- /* Return with result of call */
-
- return (invalid_timer ? ERROR : EOK);
- }
-
- /*1*********************************************************
- * Task: count_task
- *
- * Summary: Continuously counts and displays a value from
- * 0-65535.
- *
- * Caveats: Communicates with the crt_task() for displaying
- * counter values.
- ***********************************************************/
-
- void count_task(void)
- {
- unsigned char counter_buf[6];
-
- selector current_job;
- selector rsp_mbx;
- selector crt_req_mbx;
- selector init_sem;
- selector count_shtdwn_sem;
- selector req_seg;
-
- unsigned int counter;
- unsigned int status;
-
- struct CRT_REQ_STR *req_msg;
-
- struct EXCEPTIONSTRUCT exceptioninfo;
-
- /* Disable this task's exception handler */
-
- rqgetexceptionhandler(&exceptioninfo, &status);
-
- exceptioninfo.exceptionmode = 0;
- rqsetexceptionhandler(&exceptioninfo, &status);
-
- /* Lookup/Create needed objects */
-
- current_job = rqgettasktokens(THIS_JOB, &status);
-
- init_sem = rqlookupobject(current_job,
- &init_sem_name, NO_WAIT, &status);
-
- count_shtdwn_sem = rqlookupobject(current_job,
- &count_shtdwn_sem_name, NO_WAIT, &status);
-
- rsp_mbx = rqcreatemailbox(FIFO_OBJ_MBX, &status);
-
- crt_req_mbx = rqlookupobject(current_job,
- &crt_req_mbx_name, NO_WAIT, &status);
-
- /* Initialize request message */
-
- req_seg = rqcreatesegment(
- sizeof(struct CRT_REQ_STR), &status);
-
- req_msg = buildptr(req_seg, 0);
-
- req_msg->msg_ptr = counter_buf;
- req_msg->row = 12;
- req_msg->col = 38;
-
- /* Singal that initialization is completed. */
-
- rqsendunits(init_sem, 1, &status);
-
- /* Continuously count 0-65535 and display the */
- /* value until signalled to stop. */
-
- counter = 0;
-
- rqreceiveunits(count_shtdwn_sem, 1, NO_WAIT, &status);
-
- while (status == ETIME) /* while no signal received */
- {
- sprintf(counter_buf, "%d", counter);
- rqsendmessage(crt_req_mbx,
- req_seg, rsp_mbx, &status);
- req_seg = rqreceivemessage(rsp_mbx,
- WAIT_FOREVER, NUL_TKN, &status);
- counter++;
- rqreceiveunits(count_shtdwn_sem, 1, NO_WAIT, &status);
- }
-
- /* Cleanup objects created by this task. */
-
- rqdeletemailbox(rsp_mbx, &status);
- rqdeletesegment(req_seg, &status);
-
- /* Signal shutdown complete and suspend execution. */
-
- rqsendunits(init_sem, 1, &status);
-
-
- rqsuspendtask(NUL_TKN, &status); /* suspend this task */
- }
-
- /*1************************************************************
- * Task: timer_task
- *
- * Summary:
- *
- * Caveats:
- **************************************************************/
-
- void timer_task(void)
- {
- selector current_job;
- selector init_sem;
- selector clock_sem;
- selector timer_sem;
-
- unsigned int status;
-
- struct EXCEPTIONSTRUCT exceptioninfo;
-
- /* Disable this task's exception handler */
-
- rqgetexceptionhandler(&exceptioninfo, &status);
- exceptioninfo.exceptionmode = 0;
- rqsetexceptionhandler(&exceptioninfo, &status);
-
- /* Lookup needed objects */
-
- current_job = rqgettasktokens(THIS_JOB, &status);
-
- init_sem = rqlookupobject(current_job,
- &init_sem_name, NO_WAIT, &status);
-
- /* Start a one-second timer. If the timer was */
- /* created, wait for 20 seconds (units) then stop */
- /* stop the timer. */
- timer_sem = start_timer(1);
-
- if (timer_sem != NUL_TKN)
- {
- rqreceiveunits(timer_sem, 20, WAIT_FOREVER, &status);
- stop_timer(timer_sem);
- }
-
- /* Signal task finished and suspend execution. */
-
- rqsendunits(init_sem, 1, &status);
-
- rqsuspendtask(NUL_TKN, &status); /* suspend this task */
- }
-
- /*1**********************************************************
- * Functions: clrscrn, cursor_on, cursor_off, print_at
- *
- * Summary: Functions for basic display control.
- *
-
- * Invocation: clrscrn( );
- * curson_on( );
- * cursoff_on( );
- *
- * Invocation: print_at(row, col, msg_ptr)
- *
- * Inputs: row (unsigned int) - row position
- * col (unsigned int) - column position
- * msg_ptr (unsigned char *) - message string pointer
- *
- * Caveats: Escape sequences are for a WYSE60 terminal.
- **************************************************************/
-
- void clrscrn(void)
- {
- fprintf(stdout, "\x1B+");
- fflush(stdout);
- return;
- }
-
- void cursor_on(void)
- {
- fprintf(stdout, "\x1B`1");
- fflush(stdout);
- return;
- }
-
- void cursor_off(void)
- {
- fprintf(stdout, "\x1B'0");
- fflush(stdout);
- return;
- }
-
- void print_at(unsigned int row,
- unsigned int col, unsigned char *msg_ptr)
- {
- fprintf(stdout, "\x1B=%c%c%s",
- (row + 31), (col + 31), msg_ptr);
- fflush(stdout);
- return;
- }
-
- /*1*********************************************************
- * Task: crt_task
- *
- * Summary:
- *
- * Caveats:
- ***********************************************************/
-
- void crt_task(void)
- {
- selector current_job;
- selector req_mbx;
- selector rsp_mbx;
- selector rec_seg;
- selector init_sem;
- selector crt_shtdwn_sem;
-
-
- unsigned int status;
-
- struct CRT_REQ_STR *req_msg;
-
- struct EXCEPTIONSTRUCT exceptioninfo;
-
- /* Disable this task's exception handler */
-
- rqgetexceptionhandler(&exceptioninfo, &status);
- exceptioninfo.exceptionmode = 0;
- rqsetexceptionhandler(&exceptioninfo, &status);
-
- /* Lookup/Create needed objects */
-
- current_job = rqgettasktokens(THIS_JOB, &status);
-
- init_sem = rqlookupobject(current_job,
- &init_sem_name, WAIT_FOREVER, &status);
-
- crt_shtdwn_sem = rqlookupobject(current_job,
- &crt_shtdwn_sem_name, WAIT_FOREVER, &status);
-
- req_mbx = rqcreatemailbox(FIFO_OBJ_MBX, &status);
- rqcatalogobject(current_job,
- req_mbx, &crt_req_mbx_name, &status);
-
- /* Initialize display */
-
- cursor_off( );
- clrscrn( );
-
- /* Signal initialization complete */
-
- rqsendunits(init_sem, 1, &status);
-
-
- rqreceiveunits(crt_shtdwn_sem, 1, NO_WAIT, &status);
-
- while (status == ETIME)
- {
- req_seg = rqreceivemessage(req_mbx,
- WAIT_FOREVER, &rsp_mbx, &status);
-
- req_msg = buildptr(req_seg, 0);
-
- print_at(req_msg->row,
- req_msg->col, req_msg->msg_ptr);
-
- if (rsp_mbx != NUL_TKN)
- {
- rqsendmessage(rsp_mbx, req_seg, NUL_TKN, &status);
- }
- else
- {
- rqdeletesegment(req_seg, &status);
- }
-
- rqreceiveunits(crt_shtdwn_sem, 1, NO_WAIT, &status);
-
- }
-
- rqsendunits(init_sem, 1, &status);
-
- for(;;)
- {
- req_seg = rqreceivemessage(req_mbx,
- WAIT_FOREVER, &rsp_mbx, &status);
-
- if (rsp_mbx != NUL_TKN)
- {
- rqsendmessage(rsp_mbx, req_seg, NUL_TKN, &status);
- }
- else
- {
- rqdeletesegment(req_seg, &status);
- }
- }
- }
-
- /*1*********************************************************
- * Function: read_time
- *
- * Summary: Read a specified time/date register of the
- * hardware clock.
- *
- * Invocation: in_byte = read_time(reg);
- *
- * Inputs: reg (unsigned char) - the hardware time/date
- * register to be read
- *
- * Outputs: in_byte (unsigned char) - the value of the
- * register read
- *
- * Caveats: None
- ***********************************************************/
-
- unsigned char read_time(unsigned char reg)
- {
- unsigned char in_byte;
-
- outbyte(CNT_PORT, HOLD_HIGH);
- delay(3);
- outbyte(CNT_PORT, HOLD_READ);
- delay(1);
- outbyte(ADR_PORT, reg);
- delay(1);
- in_byte = inbyte(DAT_PORT);
- outbyte(CNT_PORT, CNT_RST);
- delay(1);
-
- return (in_byte);
- }
-
- /*1*********************************************************
- * Function: write_time
- *
- * Summary: Write a byte to the specified time/date register
- * of the hardware clock.
-
- *
- * Invocation: write_time(reg, out_byte);
- *
- * Inputs: reg (unsigned char) - the hardware time/date
- * register
- *
- * out_byte (unsigned char) - the value to be
- * written to the hardware clock register
- *
- * Caveats: None
- ***********************************************************/
-
- void write_time(unsigned char reg, unsigned char out_byte)
- {
- outbyte(CNT_PORT, HOLD_HIGH);
- delay(3);
- outbyte(ADR_PORT, reg);
- delay(1);
- outbyte(DAT_PORT, out_byte);
- delay(1);
- outbyte(CNT_PORT, HOLD_WRITE);
- delay(1);
- outbyte(CNT_PORT, CNT_RST);
- delay(1);
-
- return;
- }
-
- /*1*********************************************************
- * Function: read_clock
- *
- * Summary: Read the time/date from the hardware clock and
- * stores it in the global time/date structure.
- *
- * Invocation: read_clock();
- *
- * Caveats: Requires the caller to obtain access the shared
- * time/date data segment.
- ***********************************************************/
-
- void read_clock(void)
- {
- unsigned char time_buf[13];
- unsigned int i;
-
- /* Set mode */
-
- outbyte(PIO_PORT, PIO_READ);
- delay(1);
-
- /* Set control lines to disable interrupts */
-
- outbyte(CNT_PORT, CNT_RST);
- delay(1);
-
- /* Read time/date */
-
- for (i = 0 ; (i <= 12); i++)
- {
-
- time_buf[i] = read_time(i);
- }
-
- sys_time->second =
- (10 * (time_buf[SEC10_REG] & 0x07)) +
- (time_buf[SEC01_REG] & 0x0F);
- sys_time->minute =
- (10 * (time_buf[MIN10_REG] & 0x07)) +
- (time_buf[MIN01_REG] & 0x0F);
- sys_time->hour =
- (10 * (time_buf[HRS10_REG] & 0x03)) +
- (time_buf[HRS01_REG] & 0x0F);
- sys_time->date =
- (10 * (time_buf[DAT10_REG] & 0x03)) +
- (time_buf[DAT01_REG] & 0x0F);
- sys_time->month =
- (10 * (time_buf[MON10_REG] & 0x01)) +
- (time_buf[MON01_REG] & 0x0F);
- sys_time->year =
- (10 * (time_buf[YRS10_REG] & 0x0F)) +
- (time_buf[YRS01_REG] & 0x0F);
-
- /* Reset control lines to enable interrupts */
-
- outbyte(CNT_PORT, INT_ENB);
- delay(1);
-
- outbyte(ADR_PORT, 0x0F);
- delay(1);
-
- return;
- }
-
- /*1*********************************************************
- * Function: write_clock
- *
- * Summary: Uses the global time/date structure to write a
- * new time/date to the hardware clock.
- *
- * Invocation: write_clock();
- *
- * Caveats: Requires the caller to obtain access the shared
- * time/date data segment.
- ************************************************************/
-
- void write_clock(void)
- {
- unsigned char temp_byte;
-
- /* Set mode */
-
- outbyte(PIO_PORT, PIO_WRITE);
- delay(1);
-
- /* Set control lines to disable interrupts */
-
- outbyte(CNT_PORT, CNT_RST);
- delay(1);
-
-
- /* Write time/date. Seconds always set to 0 */
-
- sys_time->second = 0;
- write_time(SEC01_REG, 0);
- write_time(SEC10_REG, 0);
-
- temp_byte =
- sys_time->minute - (10 * (sys_time->minute / 10));
- write_time(MIN01_REG, temp_byte);
- temp_byte = sys_time->minute / 10;
- write_time(MIN10_REG, temp_byte);
-
- temp_byte =
- sys_time->hour - (10 * (sys_time->hour / 10));
- write_time(HRS01_REG, temp_byte);
- temp_byte =
- (sys_time->hour / 10) 0x08; /* 24hr format */
- write_time(HRS10_REG, temp_byte);
-
- temp_byte =
- sys_time->date - (10 * (sys_time->date / 10));
- write_time(DAT01_REG, temp_byte);
- temp_byte = sys_time->date / 10;
- write_time(DAT10_REG, temp_byte);
-
- temp_byte =
- sys_time->month - (10 * (sys_time->month / 10));
- write_time(MON01_REG, temp_byte);
- temp_byte = sys_time->month / 10;
- write_time(MON10_REG, temp_byte);
-
- temp_byte =
- sys_time->year - (10 *(sys_time->year / 10));
- write_time(YRS01_REG, temp_byte);
- temp_byte = sys_time->year / 10;
- write_time(YRS10_REG, temp_byte);
-
- /* Reset mode*/
-
- outbyte(PIO_PORT, PIO_READ);
- delay(1);
-
- /* Reset control lines to enable interrupts */
-
- outbyte(CNT_PORT, INT_ENB);
- delay(1);
-
- outbyte(ADR_PORT, 0x0F);
- delay(1);
-
- return;
- }
-
- /*1*********************************************************
- * Function: reset_timers
- *
- * Summary: Initializes (zero out) all timer slots of the
- * shared time/date data segment.
- *
-
- * Invocation: reset_timers();
- *
- * Caveats: Requires the caller to obtain access the shared
- * time/date data segment.
- ************************************************************/
-
- void reset_timers(void)
- {
- unsigned int i;
-
- for (i = 0 ; i < MAX_TIMERS; i++)
- {
- sys_time->timer[i].in_use = FALSE;
- sys_time->timer[i].sem_tkn = NUL_TKN;
- sys_time->timer[i].count = 0;
- sys_time->timer[i].timeout = 0;
- }
-
- return;
- }
-
- /*1*********************************************************
- * Function: advance_timers
- *
- * Summary: Increments all active timers in the shared
- * time/date data segment by one second.
- *
- * Invocation: advance_timers();
- *
- * Caveats: Requires the caller to obtain access the shared
- * time/date data segment.
- ***********************************************************/
-
- void advance_timers(void)
- {
- unsigned int i;
- unsigned int status;
-
-
- for (i = 0 ; i < MAX_TIMERS; i++)
- {
- if (sys_time->timer[i].in_use)
- {
- sys_time->timer[i].count++;
- if (sys_time->timer[i].count >=
- sys_time->timer[i].timeout)
- {
- rqsendunits(
- sys_time->timer[i].sem_tkn, 1, &status);
- sys_time->timer[i].count = 0;
- }
- }
- }
-
- return;
- }
-
-
- /*1**********************************************************
-
- * Function: advance_time_date
- *
- * Summary: Increments the time/date (hh:mm:ss mm/dd/yy) in
- * the shared time/date data segment by one second.
- *
- * Invocation: advance_time_date();
- *
- * Caveats: Requires the caller to obtain access the shared
- * time/date data segment.
- ************************************************************/
-
-
- void advance_time_date(void)
- {
- if (sys_time->second < 59)
- {
- sys_time->second++;
-
- }
- else
- {
- sys_time->second = 0;
-
- if (sys_time->minute < 59)
- {
- sys_time->minute++;
- }
- else
- {
- sys_time->minute = 0;
-
- if (sys_time->hour < 23)
- {
- sys_time->hour++;
- }
- else
- {
- sys_time->hour = 0;
-
- if (sys_time->date <
- days_in_month[sys_time->month - 1])
- {
- sys_time->date++;
- }
- else
- {
- if (sys_time->month == FEBRUARY)
- {
- /* Check for leap year (only good through 2100) */
-
- if (((sys_time->year % 4) == 0) &&
- (sys_time->date == 28))
- {
- sys_time->date = 29;
- }
- else
- {
- sys_time->month = MARCH;
- sys_time->date = 1;
-
- }
- }
- else
- {
- sys_time->date = 1;
-
- if (sys_time->month < DECEMBER)
- {
- sys_time->month++;
- }
- else
- {
- sys_time->month = JANUARY;
-
- if (sys_time->year < 99)
- {
- sys_time->year++;
- }
- else
- {
- sys_time->year = 0;
- }
- }
- }
- }
- }
- }
- }
-
- return;
- }
-
-
- /*1*********************************************************
- * Function: start_timer
- *
- * Summary: Create and start a timer.
- *
- * Invocation: timer_sem = start_timer(timeout);
- *
- * Inputs: timeout (unsigned int) - timer interval
- * in seconds
- *
- * Outputs: timer_sem (selector) - the semaphore that will
- * receive a unit at each timer interval
- *
- * Caveats: The interrupt handler will signal the
- * interrupt task to finish servicing the
- * interrupt.
- ************************************************************/
-
- /* Tell the compiler that this function is an interrupt */
- /* routine so it can generate proper cede for entering */
- /* and exiting the interrupt routine. */
-
- #pragma interrupt("clockint_handler")
-
- void clockint_handler(void)
- {
-
- unsigned int status;
-
- /* Signal the interrupt task */
-
- rqsignal interrupt(CLOCK_INT_LEVEL, &status);
-
- return;
- }
-
- /*1*********************************************************
- * Task: clock_task
- *
- * Summary: Services interrupts from the hardware clock.
- * Also maintains/updates the global time/date
- * data and services software timers.
- *
- * Caveats: Communicates with the crt_task() to display the
- * current time/date.
- ***********************************************************/
-
- void clock_task(void)
- {
- char time_string[19];
-
- unsigned int status;
- unsigned int retry;
-
- selector current_job;
- selector init_sem;
- selector clock_sem;
- selector clock_shtdwn_sem;
-
- selector rsp_seg;
- selector req_seg;
- selector crt_req_mbx;
- selector rsp_mbx;
-
- struct CRT_REQ_STR *req_msg;
-
- struct EXCEPTIONSTRUCT exceptioninfo;
-
- /* Disable this task's exception handler */
-
- rqgetexceptionhandler(&exceptioninfo, &status);
- exceptioninfo.exceptionmode = 0;
- rqsetexceptionhandler(&exceptioninfo, &status);
-
- /* Disable any current interrupt task */
-
- rqresetinterrupt(CLOCK_INT_LEVEL, &status);
-
- /* Lookup/Create needed objects */
-
- current job = rqgettasktokens(THIS_JOB, &status);
-
- init_sem = rqlookupobject(current_job,
- &init_sem_name, NO_WAIT, &status);
-
- clock_sem = rqlookupobject(current_job,
-
- &clock_sem_name, NO_WAIT, &status);
-
- sys_time_seg = rqlookupobject(current_job,
- &time_seg_name, NO_WAIT, &status);
-
- clock_shtdwn_sem = rqlookupobject(current_job,
- &clock_shtdwn_sem_name, NO_WAIT, &status);
-
- crt_req_mbx = rqlookupobject(current_job,
- &crt_req_mbx_name, NO_WAIT, &status);
-
- sys_time = buildptr(sys_time_seg, 0);
-
- rsp_mbx = rqcreatemailbox(FIFO_OBJ_MBX, &status);
-
- /* Initialize time string buffer */
-
- memset(&time_string, ' ' ,sizeof(time_string));
-
- /* Initialize crt_task() request message and */
- /* send it to the response mailbox to setup */
- /* for the processing loop. */
-
- req_seg = rqcreatesegment(
- sizeof(struct CRT_REQ_STR), &status);
- req_msg = buildptr(req_seg, 0);
-
- req_msg->msg_ptr = &time_string;
- req_msg->row = 1;
- req_msg->col = 60;
-
- rqsendmessage(rsp_mbx, req_seg, NUL_TKN, &status);
-
- /* Initialize retry counter and timers */
-
- retry = 0;
- sys_time->change_flg = FALSE;
- reset_timers();
-
- /* Initialize and read hardware clock */
-
- outbyte(PIO_PORT, PIO_READ);
- delay(1);
-
- outbyte(CNT_PORT, INT_ENB);
- delay(1);
-
- outbyte(ADR_PORT, 0x0F);
- delay(1);
-
- read_clock();
-
- /* Allow access to time/date memory */
-
- rqsendunits(clock_sem, 1, &status);
-
- /* Set interrupt vector */
-
- rqsetinterrupt(CLOCK_INT_LEVEL, 1,
-
- &clockint_handler, NUL_TKN, &status);
-
- /* Signal initialization complete */
-
- rqsendunits(init_sem, 1, &status);
-
- /* Process interrupt signals until shutdown */
-
- rqreceiveunits(clock_shtdwn_sem, 1, NO_WAIT, &status);
-
- while (status == ETIME) /* while no signal received */
- {
- /* Wait no longer than 2 seconds for interrupt signal */
-
- rqetimedinterrupt(CLOCK_INT_LEVEL, 200, &status);
-
- /* If an interrupt is NOT received, attempt to reset */
- /* the hardware clock. */
-
- if (status == ETIME) /* if no interrupt */
- {
- retry++;
-
- /* Once the maximum number of retries is reached, */
- /* give up. Retrieve outstanding display request */
- /* and wait for shutdown. When shutdown signal is */
- /* received, disable this interrupt task, signal */
- /* shutdown complete and suspend execution. */
-
- if (retry == MAX_CLOCK_RETRIES)
- {
- req_seg = rqreceivemessage(rsp_mbx,
- WAIT_FOREVER, NUL_TKN, &status);
- rqreceiveunits(clock_shtdwn_sem, 1,
- WAIT_FOREVER, &status);
- rqresetinterrupt(CLOCK_INT_LEVEL, &status);
- rqsendunits(init_sem, 1, &status);
- rqsuspendtask(NUL_TKN, &status);
- }
-
- /* Reinitialize hardware clock */
-
- outbyte(PIO_PORT, PIO_READ);
- delay(1);
-
- outbyte(CNT_PORT, INT_ENB);
- delay(1);
-
- outbyte(ADR_PORT, 0x0F);
- delay(1);
-
- /* Re-read hardware crock */
-
- rqreceiveunits(clock_sem, 1,
- WAIT_FOREVER, &status);
-
- read_clock();
-
- rqsendunits(clock_sem, 1, &status);
-
- }
- else
- {
- /* Interrupt signal received, reset retry count */
-
- retry = 0;
-
- /* Access time/date memory */
-
- rqreceiveunits(clock_sem, 1,
- WAIT_FOREVER, &status);
-
- /* If needed, update hardware clock with new */
- /* time/date data. Otherwise, advance the */
- /* time/data data by one second. */
-
- if (sys_time->change_flg)
- {
- write_clock();
- sys_time->change_flg = 0;
- }
- else
- {
- advance_time_date();
- }
-
- /* Advance any timers by one second */
-
- advance_timers();
-
- /* Prepare time/date data for display */
-
- sprintf(time_string, "%02d:%02d:%02d %02d/%02d/%O2d",
- sys_time->hour, sys_time->minute, sys_time->second,
- sys_time->month, sys_time->date, sys_time->year);
-
- /* Release time/date memory */
-
- rqsendunits(clock_sem, 1, &status);
-
- /* If previous display request has finished(segment */
- /* returned), then request new time/date be displayed */
-
- rsp_seg = rqreceivemessage(rsp_mbx,
- NO_WAIT, NUL_TKN, &status);
-
- if (status != ETIME)
- {
- rqsendmessage(crt_req_mbx,
- req_seg, rsp_mbx, &status);
- }
- }
- /* Check for shutdown */
-
- rqreceiveunits(clock_shtdwn_sem, 1, NO_WAIT, &status);
- }
-
- /* Retrieve outstanding display request, disable */
- /* this interrupt task, signal shutdown complete */
-
- /* and suspend execution. */
-
- req_seg = rqreceivemessage(rsp_mbx,
- WAIT_FOREVER, NUL_TKN, &status);
-
- rqsendunits(init_sem, 1, &status);
-
- rqresetinterrupt(CLOCK_INT_LEVEL, &status);
-
- rqsuspendtask(NUL_TKN, &status); /* suspend this task */
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Standard C
-
-
- The Header <float.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 Standard C, which he co-authored with Jim
- Brodie. You can reach him at pjp@plauger.uunet.
-
-
-
-
- Introduction
-
-
- Floating point arithmetic is complicated stuff. Many small processors don't
- even support floating point arithmetic with hardware instructions. Others
- require a separate coprocessor to handle such arithmetic. Only the most
- complex computers include floating point support in the standard instruction
- set.
- There's a pragmatic reason why chip designers often omit floating point. It
- takes about the same amount of microcode to implement floating add, subtract,
- multiply, and divide as it does all the rest of the instructions combined. You
- can essentially halve the complexity of a microprocessor by leaving out
- floating point support.
- Many applications don't need floating point arithmetic at all. Others can
- tolerate reasonably poor performance, and a few kilobytes at extra code, by
- doing the arithmetic in software. The few that need high-performance
- arithmetic often make other expensive demands on the hardware, so the extra
- cost of a coprocessor is an acceptable perturbation.
- C spent its early years on a PDP-11/45 computer. That strongly colored the
- treatment of floating point arithmetic in C. For instance, the types float
- (for 32-bit format) and double (for 64-bit format) have been in the language
- from the earliest days. (Those were the two formats the PDP-11 supported.)
- That is a bit unusual for a system-implementation language, and a "small" one
- at that. Who knows what would have happened had that machine not been equipped
- with the optional Floating Point Processor (FPP)?
- The PDP-11/45 FPP could be placed in one of two modes. It did all arithmetic
- either with 32-bit operands or with 64-bit operands. You had to execute a
- oneword instruction to switch modes. On the other hand, you could load and
- convert an operand of the wrong size just as easily as you could load one of
- the expected size. That strongly encouraged leaving the FPP in one mode. It is
- no surprise that C for many years promised to produce a double result for any
- operator involving floating point operands, even one with two float operands.
- Not even FORTRAN was so generous.
- As C migrated to other machines, this heritage sometimes became a nuisance.
- Those of us who felt obliged to supply the full language had to write floating
- point software for some pretty tiny machines. I put up PDP-11 style floating
- point on the Intel 8080 (plus Intel 8085 and Zilog Z80). I can assure you that
- it wasn't easy.
- Machines that supported floating point as standard hardware present a
- different set of problems. Chances are, the formats are slightly different.
- That makes writing portable code much more challenging. You need to write math
- functions and conversion algorithms to retain varying ranges of values and
- varying amounts of precision. I learned more than I ever wanted to know about
- portability while debugging this code.
- Machines that provide floating point as an option combine the worst of both
- worlds, at least to us compiler implementors. We had to provide software
- support for those machines that lacked the option. We had to make use of the
- machine instructions when the option was present. And we had to deal with
- confused customers who linked two flavors of code, or the wrong version of the
- library.
-
-
- Standards Issues
-
-
- From a linguistic standpoint, however, most of these issues are irrelevant.
- The main problem the drafters of the C Standard had to deal with was excess
- variety. It is a longstanding tradition in C to take what the machine gives
- you. A right shift operator does whatever the underlying hardware does most
- rapidly. So, too, does a floating add operator. Neither result may please a
- mathematician.
- With floating point arithmetic, you have the obvious issues of overflow and
- underflow. A result may be too large to represent on one machine, but not on
- another. The resulting overflow may cause a trap, may generate a special code
- value, or may produce garbage that is easily mistaken for a valid result. A
- result may be too small to represent on one machine, but not on another. The
- resulting underflow may cause a trap or may be quietly replaced with an exact
- zero. Such a "zero fixup" is often a good idea, but not always.
- Novices tend to write code that is susceptible to overflow and underflow. The
- broad range of values supported by floating point lures the innocent into a
- careless disregard. Your first lesson is to estimate magnitudes and avoid
- silly swings in value.
- You also have the more subtle issue of significance loss. Floating point
- arithmetic lets you represent a tremendously broad range of values, but at a
- cost. A value can be represented to only a fixed precision. Multiply two
- values that are exact and you can keep only half the significance you might
- like. Subtract two values that are very close together and you can lose most
- or all of the significance you were carrying around.
- Workaday programmers most often run afoul of unexpected significance loss.
- That formula that looks so elegant in a textbook is an ill-behaved pig when
- reduced to code. It is hard to see the danger in those alternating signs in
- adjacent terms of a series. Until you get burned, that is, and learn to do the
- subtractions on paper instead of at runtime.
- Overflow, underflow, and significance loss are intrinsic to floating point
- arithmetic. They are hard enough to deal with on a given machine. Writing code
- that can move across machines is harder. Writing a standard that tells you how
- to write portable code is harder still. But another problem makes the matter
- even worse.
- Two machines can use the same representation for floating point values. Yet
- you can add the same two values on each machine and get different answers! The
- result can depend, reasonably enough, on the way the two machines round
- results that cannot be represented exactly. You can make a case for truncating
- toward zero, rounding to the nearest representable value, or doing a few other
- similar but subtly different operations.
- Or you can just plain get the wrong answer. In some circles, getting a quick
- answer is considered much more virtuous than getting one that is as accurate
- as it could be. Seymour Cray has built several successful computer companies
- catering to this constituency. These machines saw off precision somewhere in
- the neighborhood of the least significant bit that is retained. Sometimes that
- curdles a bit or two having even more significance. There are even some
- computers (not designed by Cray) that scrub the four least significant bits
- when you multiply by one!
- If the C Standard had tried to outlaw this behavior, it would never have been
- approved. Too many machines still use quick and dirty floating point
- arithmetic. Too many people still use these machines. To deny them the cachet
- of supporting conforming C compilers would be commercially unacceptable.
- As a result, the Standard is mostly descriptive in the area of floating point
- arithmetic. It endeavors to define enough terms to talk about the parameters
- of floating point. But it says little that is prescriptive about getting the
- right answer.
- Committee X3J11 added the standard header <float.h> as a companion to
- <limits.h>. The latter is historically misnamed. It should more properly be
- called <integer.h>, since it deals with the properties of integer data
- representations and arithmetic. Rather than muck up an existing header (that
- we share with the POSIX Standard), we added yet another.
- We put into <float.h> essentially every parameter that we thought might be of
- use to a serious numerical programmer. From these macros, you can learn enough
- about the properties of the target machine, presumably, to code your numerical
- algorithms wisely. (Notwithstanding my earlier slurs, the major push to help
- this class of programmers came from Cray Research.)
-
-
- What The Standard Says
-
-
- The Library section of the Standard has little to say about <float.h>:
-
-
- 4.1.4 Limits <float.h> and <limits.h>
-
-
- The headers <float.h> and <limits.h> define several macros that expand to
- various limits and parameters.
- The macros, their meanings, and the constraints (or restrictions) on their
- values are listed in 2.2.4.2. [end of quote]
-
- You have to backtrack to the Environment section to get the real meat:
-
-
- 2.2.4.2.2 Characteristics of Floating Types <float.h>
-
-
- The characteristics of floating types are defined in terms of a model that
- describes a representation of floating-point numbers and values that provide
- information about an implementation's floating-point arithmetic.10 The
- following parameters are used to define the model for each floating-point
- type:
- s sign (±1)
- b base or radix of exponent representation (an integer > 1)
- e exponent (an integer between a minimum emin and a maximum emax)
- p precision (the number of base-b digits in the significand)
- fk nonnegative integers less than b (the significand digits)
- A normalized floating-point number x (â1 > 0 if x 0) is defined by the
- following model:
- Click Here for Equation
- Of the values in the <float.h> header, FLT_RADIX shall be a constant
- expression suitable for use in #if preprocessing directives; all other values
- need not be constant expressions. All except FLT_RADIX and FLT_ROUNDS have
- separate names for all three floating-point types. The floating-point model
- representation is provided for all values except FLT_ROUNDS.
- The rounding mode for floating-point addition is characterized by the value of
- FLT_ROUNDS :
- -1 indeterminable
- 0 toward zero
- 1 to nearest
- 2 toward positive infinity
- 3 toward negative infinity
- All other values for FLT_ROUNDS characterize implemention-defined rounding
- behavior.
- The values given in the following list shall be replaced by
- implementation-defined expressions that shall be equal or greater in magnitude
- (absolute value) to those shown, with the same sign:
- radix of exponent representation, b
- FLT_RADIX 2
- number of base-FLT_RADIX digits in the floating-point significand, p
- FLT_MANT_DIG
- DBL_MANT_DIG
- LDBL_MANT_DIG
- number of decimal digits, q, such that any floating-point number with q
- decimal digits can be rounded into a floating-point number with p radix b
- digits and back again without change to the q decimal digits,
- Click Here for Equation
- FLT_DIG 6
- DBL_DIG 10
- LDBL_DIG 10
- minimum negative integer such that FLT_RADIX raised to that power minus 1 is a
- normalized floating-point number, emin
- FLT_MIN_EXP
- DBL_MIN_EXP
- LDBL_MIN_EXP
- minimum negative integer such that 10 raised to that power is in the range of
- normalized floating-point numbers, élog10bemin-1ù
- FLT_MIN_10_EXP -37
- DBL_MIN_10_EXP -37
- LDBL_MIN_10_EXP -37
- maximum integer such that FLT_RADIX raised to that power minus 1 is a
- representable finite floating-point number, emax
- FLT_MAX_EXP
- DBL_MAX_EXP
- LDBL_MAX_EXP
- maximum integer such that 10 raised to that power is in the range of
- representable finite floating-point numbers, ëlog10((1-b-p) bemaxû
- FLT_MAX_10_EXP +37
- DBL_MAX_10_EXP +37
- LDBL_MAX_10_EXP +37
- The values given in the following list shall be replaced by
- implementation-defined expressions with values that shall be equal to or
- greater than those shown:
- maximum representable finite floating-point number, (1 - b-p) bemax
- FLT_MAX 1E+37
- DBL_MAX 1E+37
- LDBL_MAX 1E+37
- The values given in the following list shall be replaced by
- implementation-defined expressions with values that shall be equal to or less
- than those shown:
- the difference between 1.0 and the least value greater than 1.0 that is
- representable in the given floating point type, b1-p
- FLT_EPSILON 1E-5
- DBL_EPSILON 1E-9
-
- LDBL_EPSILON 1E-9
- minimum normalized positive floating-point number, bemin-1
- FLT_MIN 1E-37
- DBL_MIN 1E-37
- LDBL_MIN 1E-37
- Footnote:
- 10. The floating-point model is intended to clarify the description of each
- floating-point characteristic and does not require the floating-point
- arithmetic of the implementation to be identical. [end of quote]
-
-
- Implementing <float.h>
-
-
- In principle, this header consists of nothing but a bunch of macro
- definitions. For a given implementation, you merely determine the values of
- all the parameters and plug them in. A common implementation these days is
- based on a fairly recent standard for floating point arithmetic called
- IEEE-754. Older machines, such as System/370 and VAX, still have floating
- point formats that are proprietary. Newer chips, however, tend to follow this
- standard. You will find IEEE-754 floating point arithmetic in the coprocessors
- for the Intel 80X86 family and the Motorola 680X0 family, to name just two
- very popular lines. It is a very complex standard (some say too complex), but
- only its grosser properties affect <float.h>.
- The following header assumes that long double has the same representation as
- double. The former can have an 80-bit representation in the IEEE standard, but
- it doesn't have to. For this common case, you can copy the values out of an
- example in the Standard:
- /* float.h standard header -
- IEEE-754, 4/8/8
- */
- #ifndef _FLOAT
- #define _FLOAT
- /* double properties */
- #define DBL_DIG 15
- #define DBL_EPSILON
- 2.2204460492503131E-16
- #define DBL_MANT_DIG 53
- #define DBL_MAX
- 1.7976931348623157E+308
- #define DBL_MAX_10_EXP 308
- #define DBL_MAX_EXP 1024
- #define DBL_MIN 2.2250738585072014E-308
- #define DBL_MIN_10_EXP -307
- #define DBL_MIN_EXP -1021
- /* float properties */
- #define FLT_DIG 6
- #define FLT_EPSILON 1.19209290E-07F
- #define FLT_MANT_DIG 24
- #define FLT_MAX 3.40282347E+38F
- #define FLT_MAX_10_EXP +38
- #define FLT_MAX_EXP +128
- #define FLT_MIN 1.17549435E-38F
- #define FLT_MIN_10_EXP -37
- #define FLT_MIN_EXP -125
- /* common properties */
- #define FLT_RADIX 2
- #define FLT_ROUNDS -1
- /* long double properties */
- #define LDBL_DIG 15
- #define LDBL_EPSILON 2.2204460492503131E-16L
- #define LDBL_MANT_DIG 53
- #define LDBL_MAX 1.7976931348623157E+308L
- #define LDBL_MAX_10_EXP 308
- #define LDBL_MAX_EXP 1024
- #define LDBL_MIN 2.2250738585072014E-308L
- #define LDBL_MIN_10_EXP -307
- #define LDBL_MIN_EXP -1021
- #endif
- You may find a few problems, however. Not all translators are equally good at
- converting floating point literals. Some may curdle the least significant bit
- or two. That could cause overflow or underflow in the case of some extreme
- values such as DBL_MAX and DBL_MIN. Or it could ruin the critical behavior of
- other values such as DBL EPSILON.
- At the very least, you should check the bit patterns produced by these values.
- You can do that by stuffing the value into a union one way, then extracting it
- another way, as in:
- union {
- double dval;
-
- unsigned short w[4];
- } dmax = DBL_MAX;
- Here, I assume that unsigned short occupies 16 bits and double is the IEEE-754
- 64-bit representation. Some computers store the most significant word at
- dmax.w[0], others at dmax.w[3]. You have to check what your implementation
- does. Whatever the case, the most significant word should have the value
- 0x7FEF, and all the others should equal 0xFFFF.
- A safer approach is to do it the other way around. Initialize the union as a
- sequence of bit patterns, then define the macros to access the union through
- its floating point member. Since you can only initialize the first member of a
- union, you must reverse the member declarations from the example above. You
- must also give them funny names, to avoid collisions with any user-defined
- macros.
- With this approach, you would place the following definitions and declarations
- in <float.h>:
- typedef union {
- unsigned short _W[4];
- double _D;
- } _Dtype;
- .....
- extern_Dtype _Dmax, _Dmin,
- _Deps;
- #define DBL_MAX _Dmax._D;
- #define DBL_MIN _Dmin._D;
- #define DBL_EPSILON _Deps._D;
- Then in a library source file, you provide a definition for _Dmax. For the
- 80X86 family, which has the least-significant word first, you write:
- #include <float.h>
- .....
- _Dtype _Dmax = {{0xFFFF, 0xFFFF,
- 0xFFFF, 0x7FEF}};
- _Dtype _Dmin = {{0x0000, 0x0000,
- 0x0000, 0x0010}};
- _Dtype _Deps = {{0x0000, 0x0000,
- 0x0000, 0x3CB0}};
- (Warning: I haven't tested all these values, yet. Beware of errors,
- particularly of the off-by-one variety.)
- The macros you define this way have one small drawback. You cannot use them to
- initialize static data. (That includes components of aggregates such as
- structures and arrays, whether static or dynamic.) In other words, you cannot
- write:
- #include <float.h>
- .....
- static BigD = DBL_MAX;/* WRONG! */
- The macro expands to an rvalue expression, not a constant expression as
- required for a static initializer. Such a definition is permitted by the
- Standard. Only FLT_RADIX need be an expression usable in a #if directive. (An
- implementation may not change the number base it uses for floating point
- arithmetic after translation time. This was not viewed as a serious
- restriction.)
- Note, by the way, that FLT_ROUNDS can also be an rvalue expression. The
- rounding mode can change during execution. How you change it is not specified
- in the Standard, but you can test whether it has changed. Just inspect the
- current value of FLT_ROUNDS. Modern coprocessors such as the Intel 80X87
- family and the Motorola 68881 indeed permit limited program control over the
- rounding mode. The C Standard merely acknowledges reality.
-
-
- Conclusion
-
-
- Having said all this, I feel obliged to tell you to forget most of it. Only
- the most sophisticated of numerical programs care about most of these
- parameters. Only the most sophisticated of programmers know how to write code
- that adapts well to changes among floating representations.
- In all my years of scientific programming, I have found good use for only the
- *_MAX parameters. I use these to build tables for converting between floating
- point and integer. When I want the code to be highly portable, I conditionally
- include the last few entries based on the range of numbers. That is the sort
- of thing that you worry about behind the scenes. It is not likely to appear in
- a typical application.
- You should have some awareness of the peculiarities and pitfalls of floating
- point arithmetic. You should know the safe ranges and precisions for floating
- point values in portable C code and in code you write for your workaday
- machines. You can use some of the parameters in <float.h> to build safety
- checks into your code.
- But don't think that this header contains some key ingredient for writing
- highly portable code. It doesn't.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Doctor C's Pointers(R)
-
-
- Puzzles, Part 3
-
-
-
-
- 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 uunet!aussie!rex or aussie! rex@uunet.uu.net.
-
-
- This month I'll present more puzzles, though there will be a slight twist.
- These puzzles either contain syntax errors or produce unexpected output and
- are derived from problems my introductory C seminar attendees often encounter.
- This should be a lesson in "quality of implementation." That is, just how good
- are the error messages produced by a compiler, and what warning messages, if
- any, does it produce? An ANSI-conformant compiler is not required to warn
- about anything. It is permitted to say "Syntax error" once at the end of a
- compilation that detected 100 errors. It's not even required to indicate the
- offending line numbers. In short, the quality of such messages is a
- marketplace and vendor-conscience issue.
- As before, I've included the answers, but you should try to debug them on your
- own first. You might also want to see what messages your compiler produces. In
- some of the puzzles and solutions, I have included the actual messages from
- one or more DOS-based compilers. PC-Lint, a static analysis tool from Gimpel
- Software, produced the other messages. Since the primary job of lint-like
- tools is to go over your code with a fine tooth comb, it is not surprising
- that PC-Lint gave even more help than did almost all of my compilers. (While
- the exact message text has been retained, a common line number prefix has been
- added to each to make them easier to read and compare.)
- The moral of this exercise is that if you can at all afford it, buy more than
- one compiler or buy a lint-like tool in addition to your compiler. If you
- can't or won't do either of these, at least buy a compiler that has many
- lint-like features built in. (That's why I selected Turbo C when it was first
- introduced.) If nothing else, switch on the maximum warning level of the one
- compiler you do have.
- To say that you cannot afford another compiler or lint-like tool but you can
- afford to waste an open-ended number of man-hours is silly. By far, the
- biggest cost of software development is the human resources. In comparison,
- the cost of development tools is negligible, at least in PC environments and
- even in non-trivial projects on larger systems. If you nickel and dime, you'll
- get a proportional result.
- As a consultant, I initially evaluate a potential client's commitment level to
- solving his problem in a reasonable fashion by examining his investment in
- tools and training.
-
-
- The Puzzles
-
-
- 1. Incompatible return type.
- /* 1*/ struct tag {
- /* 2*/ int i;
- /* 3*/ double d;
- /* 4*/ }
-
- /* 6*/ f(struct tag *pst)
- /* 7*/ {
- /* 8*/ return (pst->i);
- /* 9*/ }
-
- line 8 - Expressions are not assignment compatible
- line 8 - 'return' : incompatible types
- 2. I don't know what I want but I don't like what I see.
- /* 1*/ #include <stdio.h>
-
- /* 3*/ void f()
- /* 4*/ {
- /* 5*/ int c;
-
- /* 7*/ While ((c = getchar()) != EOF)
- /* 8*/ putchar(c);
- /* 9*/ }
-
- line 8 - Syntax error, expected:
- ; <operator> [ ( . -> ,
- line 8 - Expecting ; but found fputc
- 3. Some comments on tokens.(See Listing 1.)
- line 15 - Syntax error, expected:
- ; <operator> [ ( . -> ,
- line 15 - missing ; before identifier printf
- line 15 - Statement missing ; in function main
- 4. Hey, where's my output?
-
- /* 1*/ #include <stdio.h>
-
- /* 3*/ main()
- /* 4*/ {
- /* 5*/ switch(6) {
-
- /* 7*/ case 2: printf("case 2\n");
- /* 8*/ break;
- /* 9*/ case 4: printf("case 2\n");
- /*10*/ break;
- /*11*/ default :printf ("default\n");
- /*12*/ break;
- /*13*/ }
- /*14*/ }
- No compilation errors or warnings were produced but there was not any output.
-
-
- The Solutions
-
-
- 1. The compiler sees that I am returning an int type expression. However, for
- some reason it is expecting something else. But what? The function is intended
- to have a return type of int. I did not explicitly declare this, but that is
- the default. I'll explicitly declare it and see what happens.
- /* 6*/ int f(struct tag *pst)
- /* 7*/ {
- /* 8*/ return (pst->i);
- /* 9*/ }
-
- line 6 - Too many types
- in declaration
- line 6 - type following
- struct is illegal
- Well, what the compiler is expecting as a return type is still not exactly
- obvious, but the structure type appears to be somehow involved. If the
- implicit return isn't int, what is it? Here's PC-Lint's hint.
- line 8 - Type mismatch (return)
- (struct = int)
- Now I know that a structure type is actually expected as the return type.
- However, it is still not obvious why since that was not my intent.
- Quite a few years ago, the ability to pass and return structures and unions
- to/from functions by value was added to the language. Consider the following:
- struct tag {
- int i;
- double d;
- };
- struct tag f(struct tag *);
- Here, f is a function taking one argument and returning a structure of type
- struct tag by value. In the rare cases you see such return types declared,
- they would be declared as shown, with the structure template separate from the
- prototype. However, both can be combined. For example:
- struct tag {
- int i;
- double d;
- } f(struct tag *);
- This is also permitted when defining a function, and that is exactly what the
- compiler saw with the original source. If you look carefully, you will notice
- that the semicolon was omitted at the end of the structure template
- definition. As such, it was seen not only as the template definition but also
- the return type of the function.
- As you might expect from this discussion, templates can also be defined within
- a function's argument list. For example:
- void g(struct tag {int i;
- double d;} *);
- From a stylistic viewpoint, I urge you to define such templates separately so
- they can easily be placed in headers.
- 2. Well, the first message isn't much help. The second is similar, but refers
- to fputc instead of putchar. With this compiler, putchar apparently is defined
- as a function-like macro that expands to a call to fputc. With maximum warning
- levels activated, the following more useful messages were produced:
- line 7 - Warning! No prototype
- found for While
- line 7 - Warning: Function While
- not declared
- Now the problem is obvious. The keyword while was spelled with a capital W.
- Since all keywords must be spelled in lowercase, the construct While (...)
- looks like a function call (to an undeclared function returning type int). The
- token putchar makes no sense at that location, so it is flagged.
- I have seen this problem arise more often with Pascal programmers, who seem to
- prefer capitalizing leading characters in identifiers. It also happens with
- If. This is a growing style with X-Windows, MS-Windows and OS/2 programmers.
- 3. Similar to the previous problem, the compiler objects to the token printf,
- not because there's something wrong with it specifically, but because such a
- token cannot be used in this context. The problem is, therefore, "Just what
- context are we in?"
- Perhaps increasing the warning level will help.
-
- line 13 -Warning: Comment within comment
- line 13 -Warning! Nested comment found in comment
- started on line 10
- Sure enough, there's something suspicious, and the second message is even more
- helpful than the first. A nested comment is a comment inside another comment.
- This might seem to be necessary in the following context:
- /* Disable the following piece
- of code
- /* ... */
- printf("Debug data is ..."};
- End disabled section */
- Neither K&R nor ANSI C permits the nesting of comments, although some
- compilers provide this as an extension.
- So what's all this got to do with our problem? We aren't trying to nest
- comments! Read the second warning message again carefully. It indicates that a
- comment was begun on line 10. Do you see it? If not, go into an infinite loop
- until you do. While the expression i/*pi states exactly what you want, the
- value of i divided by the value of the int pointed to by pi, what is actually
- seen is the token i followed by the start of a comment. This comment ends at
- the next */seen, at the end of line 13. And since another/* is seen along the
- way, the compiler warns that the start of another comment was seen inside the
- first comment.
- This kind of division expression is not particularly unusual, so how should we
- write it to get what we really want? Putting white space between the / and *
- would make it look silly, and some unsuspecting maintenance programmer may
- well remove it. The best approach is probably to use parentheses as in i/(*p),
- and to also add a comment to the effect "Don't you even think about removing
- these seemingly redundant parentheses." (Certainly, indirection has higher
- precedence than division, so the grouping parentheses are redundant from that
- perspective.)
- Regarding the nested comment issue, "If I really want to disable code
- containing comments, how do I do it?" The solution has always been to use the
- preprocessor in a non-intuitive manner. That is, to conditionally compile code
- such that it is never accepted. For example:
- #if 0 /* Disable the following piece of code/
- printf("Debug data is ..."); /* ... */
- #endif/* End disabled section */
- It's maximally portable and strange enough to get your attention. It's also
- easy to locate #if 0 with your editor to reactivate it in the future.
- 4. I admit this is a contrived puzzle -- I have never actually seen anyone
- accidentally trip over it. However, it's still interesting. Since the switch
- controlling expression matches neither of the cases, you would expect control
- to pass to the default label. Clearly this did not happen since no output
- resulted.
- According to PC-Lint though, there is something suspicious.
- line 13 - switch statement has
- no default
- line 14 - default (line 11) not
- referenced
- On the one hand, there is no default case, but on the other there is a default
- label that's not referenced. Isn't that contradictory? Well, we sometimes read
- what we want to read, not what it says. Note that the unreferenced label is
- spelled "default" not "default." The letter "L" was actually typed as the
- digit "1" making the label an unused user-defined label (suitable for use with
- goto only). Of course, being able to goto a label in the middle of a switch
- construct is very bad style, but style is the business of the programmer, not
- the language definition.
-
- Listing 1
- /* 1*/ #include <stdio.h>
-
- /* 3*/ main()
- /* 4*/ {
- /* 5*/ int i = 6;
- /* 6*/ int j = 2;
- /* 7*/ int *pi = &j;
- /* 8*/ int k;
-
- /*10*/ k = i/*pi;
-
- /*12*/ while (k > O)
- /*13*/ printf("k = %d\n", k--); /* display k */
-
- /*15*/ printf("Done\n");
- /*16*/ }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Applying C++
-
-
- Paving The Migration Path
-
-
-
-
- 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 TECH
- Specialist. 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.
-
-
- There are many good reasons to switch from C to C++. In addition to the full
- functionality of C, C++ offers stricter compile-time checking, type-safe
- linkage, inline expansion of functions, reference types, overloaded function
- names, and overloaded operators. And of course, C++ provides language-level
- support for encapsulation (data abstraction) and object-oriented programming
- (OOP).
- An unfortunate consequence of the current hype surrounding C++ and OOP is that
- some people believe that using C++ for anything other than OOP (employing both
- inheritance and polymorphism) misuses the language. I strongly disagree. OOP
- is very useful in some applications, but not all. C++ adapts well to different
- styles of programming, and OOP is just one of those styles. The language has
- much to offer, even if you're not ready for OOP.
- Still, there are some practical reasons for not using C++. C++ compilers are
- not as widely available as C compilers. Even if you can find C++ compilers for
- your target environment(s), you might not find the necessary support tools,
- such as symbolic debuggers and application libraries. A formal standard for
- C++ is still years away, and there isn't even a full implementation of the
- current de facto standard [1]. I have no doubt that the obstacles to C++ will
- eventually disappear, but that doesn't help you today.
- Even if you're not ready to move to C++, you can pave the migration path by
- writing your C code so that it also compiles as C++. C++ is an improper
- superset of Standard C; only a short list of C constructs won't compile as
- C++. Fortunately, you can easily rewrite every C construct that is not C++ as
- C that is also C++. (Note: Unless I explicitly write "Classic C", "C" means
- Standard C.)
- Tom Plum calls the common subset of C and C++ "C-". (Don't confuse this with
- "C--", Jim Gimpel's pejorative for C++ as a "strongly hyped" language [2].)
- Converting a program from C to C- is the first step in converting to C++. You
- might as well plan ahead and write all your C as C- by observing the following
- guidelines.
-
-
- Function Declarations
-
-
- Classic C has fairly relaxed rules for data type conversions. Whereas Standard
- C encourages you to get your data types right (and often warns you when you
- don't), C++ is much more insistent. C++ compilers frequently issue errors for
- transgressions that only merit a warning in C. C++ is particularly finicky
- about function declarations.
- Listing 1 shows a simple Classic C program in the lax style of the first
- edition of K&R [3]. The program compiles as Standard C, but not as C++. C++
- has at least two complaints about Listing 1: (1) greet is called before it is
- declared, and (2) printf is not declared at all. On most C++ compilers, both
- error messages say something about a missing prototype. Declaring printf is
- easy; just add
- #include <stdio.h>
- at the top of the source file. Declaring greet is a little more complicated
- and requires some decisions.
- If you move the definition for greet above main, most C++ compilers will
- accept the definition as a declaration, although they will issue a warning
- that the definition's style is obsolete. Rewrite the old-style heading
- greet (s)
- char *s;
- {
- ...
- as the prototyped heading
- greet(char *s)
- {
- ...
- to silence the warning. With a little extra thought, you might recognize
- opportunities to add appropriate const qualifiers to the parameters, such as
- greet(const char *s)
- Once greet is declared before it's used, the compiler notices that greet has a
- default return type of int, but no return expression. You can add an explicit
- void return type to the function heading, or add a return statement with a
- integral expression. In this case, the void return type is more appropriate.
- Notice that main also has a default return type of int, and no return
- expression. Curiously, most C++ compilers treat a missing return expression as
- an error, but don't even issue a warning for a missing return expression in
- main. I hope future compilers will issue at least a warning. In any event, I
- recommend explicitly declaring main as a function returning int with a normal
- return value of EXIT_SUCCESS (from <stdlib. h>).
- As an alternative to moving greet, you can insert the prototype declaration
- void greet(const char *);
- above main. However, you must still rewrite the function definition as a
- prototype to turn off the compiler's warning for using an obsolete function
- heading. Converting a C program to C-by inserting prototypes is probably
- easier than moving entire function definitions, but I prefer to move the
- functions to eliminate unnecessary prototypes. (See Listing 2.)
- In C, the function declaration
- int f( );
- means that f's parameter list is completely unspecified -- f can have any
- number of arguments of any type. To declare f with no arguments, write
- int f(void);
- In C++, both declarations for f mean that f accepts no arguments. To avoid
- confusion in C-, specify empty parameter lists explicitly using void.
- Function prototypes pose a real problem if you want your header and source
- files to compile as both Classic C and C++. Whereas Standard C supports
- prototypes but doesn't require them, Classic C doesn't support prototypes and
- C++ requires them. If you're still struggling with Classic C compilers, then
- writing in C- may be more trouble than it's worth. Nevertheless, you can use
- the preprocessor to write code that works with both Classic C and C++. See the
- sidebar, "Turning Prototypes On and Off" for details.
-
-
- Pointer Conversions
-
-
- In Standard C, assignment between different (non-void) pointer types is
- non-portable and produces a diagnostic. Most C compilers generate a warning
- against assignments like
- char *pc;
-
- int *pi;
- ...
- pc = pi;
- but compile the code nonetheless. C++ treats the assignment as an error. It is
- permitted only with an explicit cast:
- pc = (char *)pi;
- When programming in C-, don't ignore the warnings or turn them off. Rewrite
- the code to use compatible pointers or insert a cast.
- Standard C allows assignment of any pointer type both to and from void
- pointers. This allows C programmers to write functions like
- void free(void *p);
- that quietly accept any pointer type. It also permits the return value of
- functions like
- void *malloc(size_t size);
- to be copied to any pointer type without a cast. C++ considers assignment of
- void pointers to non-void pointers as a "hole" in the type safety, and permits
- it only with an explicit cast. Thus, for example, you must write the
- assignment in
- t *p;
- ...
- p = malloc(sizeof(t));
- as
- p = (t *)malloc(sizeof(t));
- to be valid in C-.
- Listing 3 shows an implementation of the Standard C function memcpy written in
- C-. C does not require the casts in the initializations of t1 and t2, but C++
- does.
- C++ allows assignment of a non-void pointer to a void pointer, but only if the
- non-void pointer points to an object that isn't const or volatile. That is,
- given
- const char name[ ] = "Dan";
- void *p;
- then neither
- p = name;
- nor
- memcpy(name, "Ben", sizeof (name));
- are not permitted in C++. (The first parameter of memcpy is a non-const void
- *). C compilers typically compile these statements with only a warning. As
- always, you can force the conversion to void * with an explicit cast.
-
-
- Enumerations
-
-
- In C, enumeration types are simply integral types. For example, given
- enum color {RED, WHITE, BLUE};
- enum color c;
- int n;
- then C permits assignments like
- c = 1;
- n = BLUE;
- C even permits
- enum day {YESTERDAY, TODAY,
- TOMORROW};
- enum day d = RED;
- although some compilers issue a warning for this assignment.
- C++ treats each enumeration as a distinct type. Assignments like
- c = 1;
- d = RED;
- are not allowed. C++ still promotes enumeration constants to integers, so
- n = BLUE;
- is legal C++.
- When programming in C-, treat each enumeration as a distinct type. However,
- you can safely assume that enumerations promote to integers.
-
-
- Linkage
-
-
- In Standard C, a global data object may be declared repeatedly without the
- extern specifier, in the same program or even in the same compilation. In C++,
- a global data declaration without extern is a definition, and each global data
- object must be defined exactly once in a program. (Note that a global data
- declaration with an initializer - with or without the extern specifier - is
- also a definition.)
- For example, suppose an integer total is shared by several source files in a
- C++ program. total must be defined in exactly one of the files using any of
- the following:
- int total;
- int total = 0;
-
- extern int total = 0;
- Any other source file that references total must declare
- extern int total;
- When programming in C-, observe the linkage rules of C++. Make sure that the
- external declarations in your header files are indeed declarations (with
- explicit extern and no initializer) and not definitions. If you use an
- external definition in a header and include that header in more than one
- source file of a C++ program, the linker will protest against multiple defined
- names.
- In C, the default linkage for global const objects is extern; in C++ it's
- static. For example, suppose file f.c contains
- const MAX = 100;
- and file g.c contains
- extern const MAX;
- If you compile and link f.c and g.c using C, the MAX in f.c has external
- linkage and provides a definition to satisfy the reference in g.c. If you
- compile using C++, the MAX in f.c has internal linkage, and the MAX in g.c is
- an unresolved reference. In C-, use explicit storage class specifiers (extern
- or static) on all global const declarations.
-
-
- Name Spaces
-
-
- C++ has 16 more keywords than Standard C:
- asm private
- catch protected
- class public
- delete template
- friend try
- inline this
- new virtual
- operato throw
- C- programs should not use these keywords as identifiers.
- C++ predefines the macro __cplusplus. Many C++ compilers (especially those
- based on AT&T's cfront translator) use names containing __ (a double
- underscore) in the translation process. C- programs should avoid identifiers
- with double underscores anywhere in the name. [In standard C, you should avoid
- even a single leading underscore. -pjp]
- C puts all ordinary identifiers (such as function, type and variable
- identifiers) in a single (scoped) name space, and puts all tags (for
- enumerations, structures, and unions) in a separate name space. Thus, for
- example, C lets you declare a structure and a function with the same name in
- the same scope, like
- struct tnode
- {
- char *word;
- struct tnode *left, *right;
- };
- int tnode(const char *s);
- Tag names in C are not type names, so you can't write
- tnode *t;
- You must carry the tag keyword around with the tag name, as in
- struct tnode *t;
- You can simplify tag references in C by defining a type name with the same
- spelling as the tag, like
- typedef struct tnode tnode;
- and write
- struct tnode *t;
- as simply
- tnode *t;
- In C++, tags are also type names. That is, you can declare
- tnode *t;
- without also defining tnode as a typedef. For compatibility with C, C++
- accepts
- typedef struct tnode tnode;
- as well as
- struct tnode *t;
- However, C++ does not allow a tag name to also be a function or variable name
- in the same scope, so that
- struct tnode;
- ...
- int tnode(const char *s);
- is invalid.
- In C-, simply define each tag name as a type name in the same scope. For
- example, declare tnode in C- as
- typedef struct tnode tnode;
- struct tnode
- {
- char *word;
- tnode *left, *right;
-
- };
- and use tnode (rather than struct tnode) in all subsequent references.
- In C, a tag name or enumeration constant defined within a struct (or union)
- has the same scope as that struct. In Listing 4, for example, tag names t and
- e and enumeration constants X and Y have the same scope as tag name s. Thus
- the declarations of ee and tt are perfectly valid, but
- const int X = 0;
- is an error in C because X is already defined as something else.
- C++ compilers interpret Listing 4 differently depending on their vintage.
- According to the C++ Annotated Reference Manual [1] (based on the AT&T 2.1 C++
- Product Reference Manual [4]), classes introduce a new scope. Since structs
- are just a special case of classes, nested type names and enumeration
- constants are local to the enclosing struct. By this rule,
- const int X = 0;
- is valid. However, the declarations of ee and tt are invalid, because
- identifiers e and t are out of scope.
- Earlier versions of C++ (including those compatible with AT&T Release 2.0)
- export tag names defined in a struct to the scope of that struct, but keep
- enumeration constants local to the struct. By these rules, the declarations of
- ee, tt and const int X are allowed, but the reference to Y in the initializer
- of ee is undefined.
- Although nested struct and enum definitions can be useful in C++, they're not
- much help in C. Given the variation in rules for nested definitions, your
- safest bet is to avoid nested definitions in C-.
-
-
- Odds And Ends
-
-
- C discards extra characters in a string initializer. For instance, C ignores
- the \0 at the end of "Dan" in
- char name[3] = "Dan";
- C++ refuses to turns its back on defenseless characters. You must write the
- initializer as
- char name[3] = {'D', 'a', 'n'};
- or face the wrath of the compiler.
- In C, sizeof('a') equals sizeof(int), but in C++, sizeof('a') equals
- sizeof(char). I have yet to encounter a situation in C- where this makes a
- difference. Please let me know if you find one.
-
-
- A Step In The Right Direction
-
-
- To a large extent, writing your C code so that it also compiles as C++ imposes
- no restrictions on the expressive power of C. Rather, it forces you to abandon
- poor coding practices, most of which are already considered obsolete by the C
- standard. The resulting code is just as efficient, but a little safer than
- Standard C, thanks to C++'s more rigorous type checking. By writing in C-, you
- are paving the road to the future. You can step up to a better C whenever
- you're ready.
- References
- [1] Ellis, Margaret A. and Stroustrup, Bjarne, The Annotated C++ Reference
- Manual. Addison-Wesley, Reading, MA, 1990.
- [2] Gimpel, Jim, "C-," The C Gazette, 4:4, Summer 1990.
- [3] Ritchie, Dennis M. and Kernighan, Brian W., The C Programming Language.
- Prentice-Hall, Englewood Cliffs, NJ, 1978.
- [4] AT&T C++ Language System Release 2.1 Product Reference Manual, AT&T, 1989.
- Turning Prototypes On And Off
- C++ requires that function declarations have prototyped parameter lists, but
- Classic C won't accept prototypes. Code that compiles as both C++ and Classic
- C must sense which language is compiling the code, and turn the prototypes on
- and off accordingly. C++ compilers predefine the macro _cplusplus, so
- #ifdef _cplusplus
- determines the current language.
- The simplest way to write a header that is both Classic C and C++ is to write
- all the function declarations twice - once with an empty parameter list and
- once with a prototyped parameter list. Select the appropriate declarations by
- testing _cplusplus, as in
- #ifdef __cplusplus
- double foo(char *, int);
- int bar(char *);
- #else
- double foo( );
- int bar( );
- #endif
- If you would also like to use prototypes when compiling with Standard C, add a
- test for the predefined macro _STDC_. You can write the test as
- #if defined_cplusplus \
- defined __STDC______LINEEND____
- but Classic C compilers might not understand the defined operator. Write the
- test as
- #ifdef __cplusplus
- #define PROTOTYPES
- #else
- #ifdef __STDC______LINEEND____
- #define PROTOTYPES
- #endif
- #endif
- To avoid writing each function declaration twice, make each parameter list
- conditional. Define a macro PROTO as
- #ifdef PROTOTYPES
- #define PROTO(x) x
- #else
-
- #define PROTO(x) ( )
- #endif
- Then write the function declarations as
- double foo PROTO((char *, int));
- int bar PROTO((char *));
- The extra set of parentheses around each parameter list transforms the
- comma-separated list of tokens into a single macro argument. Thus, if
- prototypes are available, then
- PROTO((char *, int))
- expands to
- (char *, int)
- Otherwise, it expands to just ( ).
- C++ wants you to replace old-style function definition headings like
- double foo(s, n)
- char *s;
- int n;
- {
- ...
- with prototype definitions like
- double foo(char *s, int n)
- {
- ...
- But Classic C won't accept the prototypes. To make the replacement
- conditional, write the function definition as
- #if PROTOTYPES
- double foo(char *s, int n)
- #else
- double foo(s, n) char *s; int n;
- #endif
- {
- ...
- This heading is admittedly hard to read. Since C++ only issues a warning (not
- an error) for old-style headings, you may prefer to just live with the
- warnings.
-
- Listing 1
- /*
- * Greetings in Classic C
- */
- main()
- {
- greet("Dan");
- }
-
- greet(s)
- char *s;
- {
- printf("Greetings, %s!\n", s);
- }
-
-
- Listing 2
- /*
- * Greetings in C-
- */
- #include <stdio.h>
- #include <stdlib.h>
-
- void greet(const char *s);
- {
- printf("Hello, %s!\n", s);
- }
-
- int main(void)
-
- {
- greet("Dan");
- return EXIT_SUCCESS;
- }
-
-
- Listing 3
- /*
- * memcpy in C-.
- */
- void *memcpy(void *s1, const void *s2, size_t n)
- {
- char *t1 = (char *)s1;
- const char *t2 = (const char *)s2;
-
- while (n-- > 0)
- *t1++ = *t2++;
- return s1;
- }
-
-
- Listing 4
- struct s
- {
- enum e {X, Y} b;
- struct t {int i;} a;
- };
-
- enum e ee = Y;
- struct t tt;
- const int X = 10;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Questions & Answers
-
-
- More On const
-
-
-
-
- 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 is a member on the ANSI C committee. He also does custom C
- programming for communications, graphics, and image databases. His address is
- 4201 University Dr., Suite 102, Durham, NC 27707. You may fax questions for
- Ken to (919) 493-4390. When you hear the answering message, press the * button
- on your telephone. Ken also receives email at kpugh@dukemvs.ac.duke.edu
- (Internet) or dukeac!kpugh (UUCP).
-
-
- Last month, one of the questions revolved around the const modifier.
- The const type modifier is difficult to understand, especially when combined
- with pointers. Although some authors put it first in the declaration (along
- with the storage type), I am inclined to place the const modifier after the
- data type, since that more clearly reflects what the modifier is doing.
- A non-modified type such as
- int i = 5;
- sets aside memory for the variable i and does not give any information as to
- whether the contents will be changed during the program's execution.
- int const i = 5;
- or
- const int i : 5;
- both declare that i is an integer constant. That is, its contents will not be
- modified during execution. Thus the compiler could place it in read-only
- memory (especially if it was a global or static variable) or it could use this
- information to optimize the code.
- Although you probably would not use this for a single integer in place of a
- #define, you might use this for an array or structure that was initialized and
- never altered.
- The keyword static is used for local variables whose memory is allocated at
- load time. You can initialize static variables and not change them. However
- the compiler does not prevent you from accidentally writing over their initial
- contents. The const variable is designed to protect against that occurrence.
- Its protection is compiler dependent, not absolute. A particular
- compiler/processor pair may not support read-only memory.
- The possibilities with static and const are:
- static int i = 5; /* Static variable whose
- contents may change */
- static int const i = 5; /* Static variable whose
- contents will not
- be changed */
- int const i : 5; /* Variable whose contents
- will not be changed */
- Now that I've covered the simple use of const, I would like to discuss its use
- with pointers. There are two possibilities for placing const.
- char const *pc1; /* const char
- *pc1; */
- char * const pc2;
- pc1 is a pointer that points to characters that are of type const. You cannot
- dereference the pointer (with a *) and assign something to that location. The
- following is valid:
- char const c_const;
- char const another_c_const;
- char const *pc1;
- pc1 = &c_const;
- but you cannot then assign
- *pc1 = 'A';
- since you are attempting to change the char constant. But you can perform:
- pc1 : &another_c_const;
- since the contents of pc1 are not const, only what it points to.
- On the other hand, pc2 is a constant pointer to characters that can change.
- For example, with:
- char variable_c;
- char another_variable_c;
- char * const pc2 = &variable_c;
- then
- *pc2 = 'A';
- is perfectly valid, but
- pc2 = &another_variable_c;
- is not, since you are trying to change the contents of pc2, which is to remain
- constant.
- Because of pointers, I prefer placing the const after the type. If you
- declare:
-
- const char *pc1;
- the variable pc1 is not what is constant. It is the char to which it points.
- As shown previously, the way to declare that pc1 is constant is:
- char * const pc1;
- This arrangement appears inconsistent and confusing. Using the const after the
- type follows a regular pattern. If the const keyword appears just before the
- variable name, then that variable is const.
- Note that you can assign a less constant type to a more constant type. For
- example, the following is valid:
- char *pc3;
- char const *pc1;
- pc1 = pc3;
- You just cannot use pc1 to alter any values being pointed at.
- *pc3 = 'a'; /* Valid */
- *pc1 : 'a'; /* Invalid */
- We could use const in both places in the declaration. For example:
- char const * const pc3 = c_const;
- Both of the following are invalid:
- *pc3 : 'A';
- pc3 : &another_const;
- Now for one more step. Lets look at double pointers, since that is the crux of
- your question. Given these declarations:
- char const char_const;
- char variable_char;
- char * ptr;
- char const * ptr_to_char_const;
- char * const ptr_const_to_char = &variable_char;
- char const * const ptr_const_char_const;
- You have at least the following possibilities for the declaration and
- assignment of the double pointer:
- char ** ptr_to_ptr_to_char;
- char const ** ptr_to_ptr_to_char_const;
- char * const * ptr_to_ptr_const_to_char;
- char ** const ptr_const_to_ptr_to_char = &ptr;
-
- ptr_to_ptr_to_char = &ptr;
- ptr_to_ptr_to_char_const = &ptr_to_char_const;
- ptr_to_ptr_const_to_char = &ptr_const_to_char;
- Notice that the last of these declarations has to include the initialization,
- since the variable itself is being declared as const. I won't go into an
- explanation of all of these. You can work out the remaining possible variable
- declarations as:
- char const * const * ptr_to_ptr_const_to_char_const;
- When a pointer to const appears in a function's parameter declaration, it
- implies that the function will not change the variable pointed at by the
- parameter that is passed. The actual variable pointed at by the parameter can
- be a non-const variable.
- This is not needed for the parameters themselves, as a copy is always passed
- to the function. With ints, for example, it is redundant to state in a
- prototype:
- int integer_compare_function ( int const integer1,
- int const integer2);
- For string pointers, which may be changed by the called routine, it is also
- redundant to state:
- int change_strings(char * const string);
- Although it is extraneous in the prototype, you may wish to specify the
- parameters in the function header this way. Inside the function you would not
- be able to accidentally alter their values.
- If the actual strings are not going to be changed, then you would code the
- prototype as:
- int string_compare_function(char const *string1,
- char const *string2);
- Inside the function, you could not make assignments as:
- *string1 = 'a';
- but you could perform:
- string1 = string2;
- If the function were being passed handles (pointers to pointers), then the
- function could be prototyped as:
- int string_handle_compare_function(char const * const
- * string1, char const * const * string2);
- You would not be permitted to use either:
- * string1 = *string2;
- **string1 = 'a';
-
-
- Readers' Replies
-
-
-
-
-
- Indentation
-
-
- Generally, I find your column most interesting. Keep up the good work.
- However, I was a bit disappointed by your comments on Brian S. Merson's letter
- in the September issue.
- You said, "Your style has a lot going for it." While there is nothing
- particularly bad about his style of indentation, it seems to me to be a
- classic case of a solution to a non-problem for two reasons:
- 1) Even given his program as it stood, he could just have drawn a line to the
- left of the braces which would not then have run through the first character
- of the code. Ensuring that all the code was legible would seem to be a basic
- precaution taken before debugging.
- 2) What are aflag and eflag doing in a serious program? Variables should have
- meaningful names with a minimum Hamming distance between them, i.e., they
- should differ in a number of positions.
- Donal Lyons
- Dublin, Ireland
- The style to which you refer in #1 looked like:
- for (...)
- {
- /* Internals indented one space to right of brace */
- }
- I agree with you regarding #2. That's why I started the Obscure Name Contest.
- (KP)
-
-
- Pointer Blues
-
-
- There may be a simpler explanation than "pointer blues" for the problem
- mentioned by Mark Petrovic in the Q?/A! column in The C Users Journal
- September 1990. He reported that some simple programs did not run correctly
- until a printf() statement was added for debugging. Some C output functions
- are buffered and do not appear to work when stepping through a program in
- debug mode, until the buffer is dumped -- which a printf() statement does. His
- description of the way in which the programs didn't work isn't detailed enough
- to know if this is what he was encountering.
- Janet Price
- Kalamazoo, MI
- You are correct. The printf buffer is usually not dumped to the screen until a
- \n appears. Without the code though, it is difficult to state precisely what
- went on. (KP)
-
-
- repeat_format
-
-
- In your Q?/A! column (The C Users Journal, September 1990, page 111), I
- noticed another reader (Doug Oliver, Wichita, KS) had written a function,
- repeat_format, which expands format strings with embedded repeat
- specifications to make printf-compatible format strings. He pointed out that
- the function returned the address of data local to repeat_format, which
- technically is not guaranteed valid, but which may be copied and used anyway.
- The reason this data may be so used has to do with stack usage. Each time the
- function repeat_format is called, space is reserved on the stack for local
- data. My compiler (Turbo C 1.5), and perhaps his (he used QuickC), places the
- string space for newfmt[] farthest away in the stack area from BP (the
- processor register relative to whose value local variables and parameters are
- referenced in the compiled functions' code). Also, the 256 bytes reserved for
- newfmt[] in repeat_format is nearly 100 bytes longer than any of the format
- strings generated by the sample driver.
- In the sample driver and in the recursive calls within repeat_format, it is
- used as the source parameter to strcpy, e.g. strcpy(newstr,
- repeat_format(fmtstr[i])), whose stack uses a portion of repeat_format's
- now-invalid data area. Fortunately, in this case other local variables, which
- are not used for anything after the function returns, are the victims.
- To verify this, I changed a copy of the C source so that newstr[152] was used
- as the last local data item declared in repeat_format. (152 is only slightly
- longer than the minimum string length necessary to hold the longest format
- string returned to the sample program.) The result was that, when run, garbage
- was displayed at the end of the longest format string. As long as
- repeat_format is left in its original form, and used as a source parameter for
- strcpy, it should be safe-tempstr[]. The other local variables provide more
- than enough room for strcpy to execute safely.
- It should be noted, though, that the function is somewhat stack-hungry,
- requiring approximately 350 bytes minimum per call, and could run into trouble
- in some memory models if deeply-nested format strings were expanded in a
- data-intensive application.
- I also found it interesting to use this function with dprintf, which also
- appeared in The C Users Journal (September 1990). By modifying dprintf so that
- it uses repeat-format to expand the format string passed to it, and passing
- this result to vdprintf, format strings with repeat format specifiers may be
- treated as ordinary format specifiers in calls to dprintf.
- While testing these functions, I decided to check them for Microsoft
- compatibility with Learn C, a subset of QuickC developed by Microsoft and
- marketed by Microsoft Press. Unfortunately, dprintf would not run under Learn
- C, but, instead, generated the message unresolved external: ftol. I
- subsequently found out that it would not convert from float to int. I am still
- waiting to hear from Microsoft about the problem as of this writing.
- Thank you. I enjoy your column and The C Users Journal.
- John W. Bandy
- Armuchee, GA
- Thank you for your analysis of the code. Using the address of an automatic
- variable, when that variable is no longer allocated, is a cause for
- indeterminate code. The algorithm here will work as long as the
- processor/compiler does not make the previously allocated storage unavailable
- (i.e., to cause an addressing exception to occur).
- One could allocate a large static buffer to fill up with the generated string
- and return the address of that buffer. This is the way that some standard
- library routines work. Whether statics or automatics are used, the routine
- should check to see it does not exceed the length of the array. Some compilers
- allocate automatic variables on the same stack used for return addresses.
- Exceeding array limits causes interesting debugging problems on those
- machines. (KP)
-
-
- External Identifiers
-
-
- I would like to address Andreas Lang's question in the October 1990 issue
- about keeping all public variables, extern or not, in one header file.
- Learning from other programmer's code and coming up with a few ideas of my
- own, I have developed a scheme for just this purpose.
- Listing 1 shows how I would write the example in question. MAIN is #defined in
- a file that includes this header file, EXT is expanded to nothing, and the
- initialization is taken by the preprocessor. Otherwise, EXT is expanded to
- extern and the initialization is skipped.
- I have found this scheme very useful, I can keep a single header file for
- variables and initialize them too.
- Bill Sharar II
- Denton, Texas
- I was very much interested in your response to Andreas Lang in the October
- 1990 C Users Journal. He is attempting to create a single header file to be
- shared among any number of source files. This header file would allow
- reference to the global variables for the program by declaring them as
- external to all the source files except for one (the "main"source file). His
- attempt works well except in the case where a global variable must be
- initialized. I have another solution to the problem, which, though inelegant,
- would allow him to do what he wishes.
- Using his example, test.h would look like Listing 2.
- The remainder of his example would be unchanged. (I find the use of the
- keyword Global clearer than the EXTERN used in the example. It better defines
- what we are trying to do, which is declare a global variable.)
- Yes, it is kind of ugly, but it does work (at least when using Microsoft C
- v5.1). It permits the same code string to declare, define, and initialize the
- variable as needed, since everything is in one place, with all the associated
- maintenance benefits.
- In that same issue, in your response to Frederick C. Smith, you state that
- stderr cannot be redirected under MS-DOS. This statement requires
- clarification. While it is true that there is no command-line facility
- available to redirect stderr, such as >& in the C-shell under UNIX,
- redirection under program control is possible by calling the freopen function:
- freopen("error.log", "w", stderr);
-
- system("cmd");
- (These functions do return values that should be checked, but I omitted this
- for the sake of brevity.)
- The previous code sequence would redirect to the new file error.log, then run
- the program cmd. Any error messages from cmd would then be redirected to
- error.log (assuming they were written to stderr).
- I look forward to your column each month in The C Users Journal. I find it
- educational and well-written, and like the rest of the magazine, well worth
- reading. Thank you.
- David Hansen
- I think I have a solution to Andreas Lang's problem (October 1990) about
- initializing external variables in header files without having to write them
- in any other files.
- The macros EXTERN and INIT are defined in the header file as shown in Listing
- 3. The variable is then written in the header file as:
- EXTERN int i INIT(5);
- So, in the main file, the preprocessor produces:
- int i = 5;
- and in all other files it produces:
- extern int i;
- Note that the INIT macro will work independent of val's type (I think). Is
- there anything questionable (or just plain wrong) about this method?
- Larry Leonard
- Norcross, GA
- The only problem with this method is that it looks awkward when initializing
- an array or anything that requires more than one line of initial values. For
- example:
- EXTERN int array[15] INIT({1,2,3,4,5,6,7,8,9,10,\
- 11,12,13,15});
- Notice both the braces next to parentheses and the need for the \ to carry the
- initializing string to the next line. [Some older compilers required the
- backslash, but it is not required in standard C. - pjp]
- Even given the previous three responses, I think I still prefer using a single
- header file that contains the variable declarations with the keyword extern
- and a separate source file with the declarations and initial values.
- Most of the time I try to avoid using global variables entirely and use access
- routines that look like:
- static int global_variable = 5;
- set_global_variable (value)
- {
- global_variable : value;
- }
- get_global_variable(value)
- {
- return value;
- }
- There is no header file to include and only one place where the definition has
- to appear. (KP)
-
- Listing 1
- #ifdef EXT
- #undef EXT
- #endif
-
- #ifdef MAIN
- #define EXT
- #else
- #define EXT extern
- #endif
-
- EXT int i
- #ifdef MAIN
- = 5
- #endif
-
-
- Listing 2
- #ifdef MAIN
- #define Global
- #define INIT_GLOBAL
- #else
- #define Global extern
- #endif
-
- Global int i
- #ifdef INIT_GLOBAL
-
- = 5
- #endif
- ;
-
-
- Listing 3
- /* ---------------------- CUJ.H ----------------------------- */
-
- #ifdef MAIN
- #define EXTERN
- #define INIT(val) = val
- #else
- #define EXTERN extern
- #define INIT(ignored)
- #endif
-
- EXTERN int i INIT(5);
-
- /* ---------------------- CUJ.C ----------------------------- */
-
- #include <stdio.h>
- #define MAIN
- #include "cuj.h"
-
- main()
- {
- printf("The value of i in main() is %d.\n", i);
- cuj_fnx();
- }
- /* ---------------------- CUJ_FNX.C ------------------------- */
-
- #include <stdio.h>
- #include "cuj.h"
-
- cuj_fnx()
- {
- printf("The value of i in cuj_fnx() is %d.\n", i);
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Implementer's Notebook
-
-
- Expect
-
-
-
-
- Don Libes
-
-
- Don Libes is a computer scientist at the National Institute of Standards and
- Technology. He is also the author of Life With UNIX, published by
- Prentice-Hall. His electronic mail address is libes@cme.nist.gov. He can also
- be reached at NIST, Bldg. 220, Rm A-127, Gaithersburg, MD 20899.
-
-
- In my September 1990 column, I discussed the embeddable language, Tcl (Tool
- Command Language), written by John Ousterhout at University of California at
- Berkeley. In that column, I described how to build tools using Tcl. I recently
- built a tool using Tcl called expect, which has become quite popular. I would
- like to share some of the internals of it with you.
- Briefly, expect can play the role of a user in an interactive program. In
- effect, it can force an interactive program to be non-interactive! The first
- problem I applied expect to was (of course) a game: Rogue.
-
-
- Automating Rogue
-
-
- Rogue is an adventure game that presents you with a player who has various
- physical attributes. One attribute is a strength rating. Most of the time the
- strength is 16, but every so often -- maybe one out of 20 games -- you get an
- unusually good strength of 18. Many people know this, but no one restarts the
- game 20 times to find those really good configurations. Well, the expect
- script in Listing 1 can do it for you.
- The script works as follows: Inside a for loop, Rogue is started by using the
- spawn keyword. The strength is checked to see if it is either 18 or 16. If it
- is 16, the next statement in the script is executed, closing the connection
- and effectively sending an EOF to Rogue, which goes away. The loop is then
- restarted, and a new game of Rogue of run. When a strength of 18 is found, you
- break out of the loop and drop down to the bottom of the script, executing the
- next line, which says "interact". Executing this line passes control back to
- the real user, allowing him to play this particular game.
- If you run this script, you'll actually see 20 to 30 initial configurations
- fly across your screen in less than a second, finally stopping with a great
- game for you to play. The only way to play Rogue better is under the debugger!
-
-
- The expect Command Described
-
-
- I'm not going to explain all of expect. The Rogue script should give you
- enough of a taste. Instead, I'm going to focus on the most interesting part:
- the expect command itself. It illustrates a number of concepts, plus it is
- real code that you might like to plug into your application, with or without
- the rest of the expect program.
- The expect command reads from the output of another process. The output is
- examined for a pattern that matches any of the patterns supplied as arguments
- to the command itself. Expect takes arguments as pattern-action pairs. When
- any of the patterns match, the corresponding action is executed. Actions are
- just Tcl statements (such as a send, or even another expect command). As in C,
- compound statements may be grouped by enclosing them in braces.
- In the script above, there are two patterns. When *Str: 18* is seen, the break
- statement is executed. When *Str: 16* is seen, nothing (the null action) is
- executed. (The * matches anything.)
- One other feature not illustrated by the script is that the keywords eof and
- timeout are special patterns. If an EOF appears in the stream, the action
- paired with the eof pattern (if there is one) is executed. Similarly, if a
- certain time period expires without matching any patterns, the timeout action
- is executed.
-
-
- The expect Command Implemented
-
-
- Expect follows the usual Tcl calling conventions, which are shown in Listing
- 2. The first argument is a programmer-supplied pointer (unused here, hence the
- ARGSUSED comment to lint.) The second is a pointer to an interpreter instance
- (always the same here). The third and fourth arguments are the command
- arguments that the script supplied, presented in argc/argv style.
- Immediately upon entry, the arguments are checked for correct usage. (See
- Listing 3.) In this case, all we have to do is check for at least one pattern.
- Expect assumes that a final missing action is just the null statement. This
- means you can write statements like expect foo, which just delays until foo or
- EOF appears, or the timeout occurs.
- The tcl_error function saves a message to be passed back to the interpreter
- evaluating each Tcl statement. It will print out the message when it sees
- TCL_ERROR returned.
- Next, the stream is selected. The script can switch between streams by setting
- the Tcl variable spawn_id (also set by the spawn command). Similarly, the
- timeout period is specified by setting the Tcl variable timeout. Listing 4
- shows how we retrieve and use these values from Tcl. (The third argument
- indicates scoping, but will not be further explained here.) These values are
- not passed as parameters because they are expected to change infrequently.
- Thus, they are changed by giving an explicit command (e.g., set timeout 60.)
- The script controls one other variable. match_max defines the number of
- characters that expect guarantees it will use to match patterns. The default
- is 2,000. If patterns must match more than 2,000 bytes, the script must raise
- this value explicitly. expect doesn't automatically use a very large value,
- since that can needlessly slow down the pattern matching process. (More on
- this later.)
- Fetching and resetting the buffer is shown in Listing 5. realloc is called on
- the presumption that if the buffer shrinks, the function is likely to
- efficiently return the same buffer. Similarly, if the buffer grows back, the
- function will return the same buffer, if possible. The logic actually
- allocates space that is twice what the script asked for, so expect can avoid
- dealing with matching output that straddles two buffers.
- The next step is to massage the patterns and actions into a more usable form.
- The code in Listing 7 does just this by picking up the original arguments and
- placing them into a structure declared in Listing 6.
- At this point, expect can begin looking for patterns. The code in Listing 8
- begins by zeroing buf, the buffer that accumulates the incoming bytes. Then
- the code loops. Basically, the loop consists of the following steps:
- 1) read as much new data as is available, blocking if none.
- 2) break out of loop if eof, timeout or any patterns match.
- Naturally, the implementation is a little more complicated than that. In the
- loop, first make sure there is space in the buffer for incoming data. (rc is
- the number of characters in the buffer.) To guarantee that matches can occur
- on data of a minimum length (match_max), copy the second half over the first
- if the buffer is full. This is why the original request was doubled earlier.
- Next the function i_read is called. The arguments to i_read are the stream to
- read, where in the buffer to put new bytes, how many to read, and how long to
- wait for them. Internally, i_read starts an alarm and then begins reading. If
- the read completes, the alarm is cancelled and i_read returns the numbers of
- characters read. Otherwise, the alarm interrupts the read, and i_read returns
- -2 to denote that. (-1 is returned for other errors, and 0 is returned for
- EOF.) Hence i_read is an "interruptible read".
- I don't have the space in this column to show the implementation of i_read. If
- you are interested, I devoted a whole column to it in the Micro/Systems
- Journal (September 1986).
- The result of i_read determines whether an EOF or timeout occurred or whether
- some other abnormal event happened. In all of these cases, the loop is exited.
- If any characters were read, the buffer end is updated. The new characters are
- echoed to stdout, if desired. (The script has control over this via the C
- variable loguser, which is mapped to a Tcl function.)
- At this point, all that is left is to check if the output matches the
- patterns. A slight problem presented by Tcl is that it uses the C convention
- of null-terminating strings. Thus, any nulls must be removed by calling
- rm_nulls (which returns the numbers of nulls removed). rm_nulls is not shown
- here, but it is trivial to write. Notice that this step is done after the
- write to stdout, because it is likely that the nulls are involved in screen
- formatting operations.
- The final operation compares the patterns against the input, by calling the
- patternmatch function, which understands the "usual" wildcards and C escapes.
- Like i_read, the implementation of patternmatch is not relevant here. If a
- pattern matches, the loop is aborted. Notice that goto is used to break out of
- two for loops. This is one of the few acceptable reasons to use goto in a C
- program, since without it, the code would be harder to read.
- Lastly, notice that the keyword cases are skipped when looking for keywords.
- See Listing 8.
- If the loop terminates, expect has either timed out, seen a pattern or EOF, or
- encountered some error. In the last case, the function simply cleans up, and
- returns an error indication to the caller. Otherwise, expect selects the
- appropriate action, passing it to Tcl_Eval for execution, and returning the
- result of Tcl_Eval to your caller. (This allows Tcl to automatically handle
- statements like "break" in Listing 1.) The input buffer is also made available
- in the Tcl variable expect_match. Using this, the script can find out what
- matched a pattern (or what failed to match after a timeout).
-
- Lastly, the pattern-action structure is freed, and the result is returned. See
- Listing 9.
-
-
- Some Less Important Notes
-
-
- I have simplified the code, primarily for readability. For instance, the
- quoted strings in the original are defined by C preprocessor definitions.
- Patterns can actually be lists of terns, permitting multiple patterns to
- trigger the same action. This complicates the pair structure and everything
- that touches it, without adding any interesting programming, so I've omitted
- it here. I also removed some logging code. The logging code can record the
- interaction, which is primarily useful in debugging. expect can also be turned
- back on a real user, in which case it shouldn't echo the user's input since he
- is already seeing it (having just typed it). This makes the logging code even
- more complex.
- Note also that some initialization must occur before cmd-Expect is ever
- executed. For instance, we guarantee that the Tcl variables timeout and
- spawn_id have values by setting them initially ourselves.
- Please forgive me for omitting most of the declarations. They should be
- obvious. One that I must comment on is the declaration for stream. It is a
- FILE *. It is interesting because the script can change it by accessing the
- Tcl variable spawn_id. Nothing else can be done with it. To the script, it
- serves only as a magic cookie that can be used to select which process to
- interact with. In the script's world, it appears as a rather odd string, but
- inside expect, it becomes a perfectly usable stream. (Actually, it is a magic
- cookie as far as I'm concerned too, albeit a different kind. We can pass it to
- a limited set of functions, but nothing else.)
-
-
- Conclusion
-
-
- I hope you've enjoyed exploring this interesting function of a very real
- program. This particular function draws together a large number of techniques
- to accomplish a coherent result. Whether you decide to incorporate a function
- like this one in your code, or just use some of the techniques (such as Tcl),
- you have seen one way of going about it. Being production code, this has also
- shown you all the details that must be taken into account.
- Expect is in the public domain. To date, more than 1,000 people have requested
- and received copies of expect, and there are no known bugs. If you would like
- your own copy of expect, you can ftp it as pub/expect.shar.Z from
- durer.cme.nist.gov. You can request e-mail copies by mailing to
- library@cme.nist.gov. The contents of the message should be (no subject line)
- send pub/expect.shar.Z. Two conference papers on expect exist as
- pub/expect.ps.Z and pub/expect-sysadm.ps.Z.
- Expect requries a multitasking operating system. Because expect is a process
- that controls other processes, it must be able to run multiple processes
- simultaneously. While expect is used primarily on UNIX systems, I would be
- willing to help you complete ports to other systems.
-
- Listing 1
- for {} 1 {} {
- spawn rogue
- expect "*Str: 18*" break \
- "*Str: 16*" {}
- close
- }
- interact
-
-
- Listing 2
- /*ARGSUSED*/
- int
- cmdExpect(clientData, interp,
- argc, argv)
- ClientData clientData;
- Tcl_Interp *interp;
- int argc;
- char **argv;
- {
-
-
- Listing 3
- if (argc < 2) {
- tcl_error("usage: expect
- [pattern action] ...
- pattern [action]");
- return(TCL_ERROR);
- }
-
-
- Listing 4
- stream = atoi(Tc1_GetVar(interp,
- "spawn_id",0));
- timeout = atoi(Tcl_GetVar(interp,
- "timeout",0));
-
-
- Listing 5
- s = Tcl_GetVar(interp,"match_max",0);
- if (buf_size != (new_size = 2*atoi(s))) {
-
- if (0 == (new_buf = realloc(buf,new_size+1))) {
- tcl_error("failed to grow match buf to %d bytes",
- new_size);
- return(TCL_ERROR);
- }
- buf_size = new_size;
- buf = new_buf;
- }
-
-
- Listing 6
- typedef struct {
- char *pattern;
- char *action;
- enum {keyword, pattern} type;
- } pair;
-
-
- Listing 7
- pairs_inuse = argc/2; /* number of patterns */
- if (0 == (pairs = (pair *)malloc(pairs_inuse * sizeof(pair)))) {
- tcl_error("malloc(%d pairs)",pairs_inuse);
- return(TCL_ERROR);
- }
-
- timeout_action = eof_action = 0;
- for (i = 1, p = pairs;i<argc;i+=2,p++) {
- if (!(strcmp(argv[i],"timeout"))) {
- p->type = keyword;
- timeout_action = argv[i+1];
- } else if (!(strcmp(argv[i],"eof"))) {
- p->type = keyword;
- eof_action = argv[i+1];
- } else {
- p->type = pattern;
- p->pattern = argv[i];
- p->action = argv[i+1];
- }
- }
-
-
- Listing 8
- buf[0] = '\0';
-
- for (;;) {
- if (rc == buf_size) {
- memcpy(buf,buf+buf_size/2,buf_size/2);
- rc = buf_size/2;
- }
-
- cc = i_read(stream,buf+rc,buf_size-rc,timeout);
-
- if (cc == 0) { /* normal EOF */
- eof = TRUE;
- fclose(stream);
- break;
- } else if (cc == -1) { /* abnormal EOF */
- if (i_read_errno == EBADF) {
- tcl_error("bad spawn_id (process died earlier?)");
-
- } else {
- tcl_error("i_read(spawn_id=%d): %s",
- stream,sys_errlist[errno]);
- fclose(stream);
- }
- error = TRUE;
- break;
- } else if (cc == -2) break; /* timed out */
-
- oldrc = rc;
- rc += cc;
-
- if (loguser) {
- fwrite(buf+oldrc,1,cc,stdout);
- fflush(stdout);
- }
-
- rc -= rm_nulls(&buf[oldrc],cc);
- buf[rc] = '\0';
-
- for (p=pairs;p<&pairs[pairs_inuse];p++) {
- if (p->type == keyword) continue;
- if (patternmatch(buf,p->pattern)) {
- match = TRUE;
- goto done;
- }
- }
- }
-
-
- Listing 9
- done:
- if (error) result = TCL_ERROR;
- else {
- char *action;
-
- if (match) action = p->action;
- else if (eof) action = eof_action;
- else action = timeout_action;
-
- if (action) result = Tcl_Eval(interp,action,0,(char **) NULL);
- else result = TCL_OK;
-
- Tcl_SetVar(interp,"expect_match",buf,0);
- }
-
- free((char *)pairs);
-
- return(result);
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Complex Arithmetic And Matrices In C + +
-
-
- Louis Baker
-
-
- Louis Baker has a Ph.D. in astronomy and has written books with code in C and
- Ada for McGraw-Hill. He may be reached at Mission Research Corp., 1720
- Randolph SE, Albuquerque NM 87106.
-
-
- In the May issue, I presented header files designed to simplify the
- programming of complex arithmetic and matrices in C. In this article, I will
- demonstrate that using C++ could further simplify such programming.
- Developing these programs was my first foray into C++. By mentioning some of
- my misconceptions and stumbling blocks, I hope to ease the way of other C
- programmers taking up C++. After developing these programs with Zortech C++, I
- got my hands on Turbo C++. I will compare the two and comment on porting
- between C++ compilers.
-
-
- Complex Arithmetic
-
-
- In my previous article (May 1990), I gave an example of a program to compute a
- type of Bessel functions, called Kelvin functions, using complex arithmetic.
- (If you aren't familiar with complex numbers, see the previous article.) Here,
- you will use a header file, COMPLEX.HPP (Listing 1), to facilitate complex
- arithmetic, which employs operator overloading. This header enables you to add
- two complex numbers, a and b, to produce c, with code of the form c=a+b rather
- than CADD(c,a,b) as was required with COMPLEX.H. The code is straightforward
- and needs no discussion. The simplest routines are inline for efficiency.
- I will illustrate using the code by calculating the Hurwitz Zeta function
- z(s,a), which is used in various applications, including summing series and
- number theory. It is defined as
- Click Here for Equation
- The Riemann zeta function is z(s,1). Spanier and Oldham give an efficient
- formula for evaluating this function, which contains two summations and one
- additional term. The first summation is finite, with specified upper limit,
- and the second is infinite, with the terms depending on the upper limit
- specified for the first sum. They then set the first sum equal to zero, and
- use a rational approximation method to obtain the second sum. There is an
- associated cost: for n terms, order n2 operations are required vs. order n for
- summing the series. The added operations include division. The rational
- approximation generally converges more rapidly than summing the series, except
- for large s. I have made two changes to their method. First, I have
- re-introduced the first summation, which improves behavior for large s.
- Second, while they computed the second sum for a fixed number of terms, I
- check the convergence of that sum and terminate when a given tolerance is
- achieved. I limit the sum to a maximum of 80 terms to prevent problems with
- overflow. The program, along with support routines for computing and printing
- the logarithm and exponential of a complex number, as its absolute value
- (magnitude) is given in Listing 2. Note the need to delete the storage
- allocated to the h array.
-
-
- Vectors And Matrices
-
-
- Vectors and matrices of complex numbers can take advantage of C++ features,
- notably operator overloading, to ease the programmer's burden. While a vector
- can be implemented as a matrix with a single row or column, it is more
- efficient to treat it as a distinct class. Listing 3, CVECTOR.HPP, defines the
- vector class and Listing 4, CMATRIX.HPP, the matrix class, with the functions
- and test program main() in Listing 5, CMATRIX.CPP. You can find a number of
- matrix class implementations in Using C++ by Bruce Eckel, and in J. F.
- Dreitlein and J. R. Sauer's article, "Spinor Software Tools in C++" in
- Computers in Physics magazine (see Reference at the end of this article). I
- was initially tempted to implement the matrix as a vector of row vectors of
- class Cvector, but I decided to follow the usual practice (as in the two cited
- references) of using a single variable of type complex **head. With this
- practice, the natural notation head[i][j] represents the appropriate matrix
- element.
- The classes Cvector and Cmatrix carry along an integer variable named base.
- The default value, 0, gives the usual C convention of vectors with a first
- element of index 0. Set base=1 for the FORTRAN convention of first element
- having the index 1. The latter choice might be useful in converting FORTRAN
- code or a mathematical formula with the usual notation of row and column
- indices beginning at 1.
- Eckel implements matrices by doing his own memory management. Each matrix has
- a reference count, and the destructor deletes the storage required only if
- this count is one. He also copies matrices by assigning the pointer and
- increasing the reference count. This method is economical in time, and
- probably storage, but it is dangerous. Altering one reference to the matrix
- will alter all other copies. The reference count method of storage allocation
- can sometimes fail to free storage when it is possible to do so, for example
- if there is a circular chain of references.
- A safer method is to define the copy operators Cmatrix(Cmatrix&) and
- Cvector(Cvector&) to allocate new memory and copy all the data. This costs
- time and perhaps space, but avoids the two problems mentioned for the other
- scheme. Obviously, there are time-critical applications in which the
- pointer-copying approach could be desirable. Depending on the application, the
- extra memory occupied by multiple copies may or may not outweigh the space
- occupied by the reference count in each object, and any unrecovered memory due
- to circular chains.
- The package CMATRIX.HPP implements the most basic operations, including taking
- the dot product of two vectors, and multiplying two matrices or a vector by a
- matrix. Eckel's package contains a large number of routines, includes
- factoring matrices into their LU decomposition, performing statistical
- operations, and interchanging rows or columns. Interested readers might start
- by converting the Basic Linear Algebra Subroutines (BLAS) package to C++, and
- using its functions to implement more complicated matrix operations.
-
-
- Inheritance
-
-
- I had originally intended to use inheritance, but discovered it wasn't very
- useful for the problems at hand. For example, I couldn't use complex as a base
- class from which complex vectors were derived. I could have defined vectors
- based at 0 as the base class, and define a class with arbitary bases that
- inherits from that base class and adds the integer variable base. This might
- save some storage and arithmetic if the user wants to use indices based at 0,
- but it seems more sensible to "do it right" from the start.
- I found the data hiding features of C++ unhelpful at best. For example, I had
- originally placed the function error() as a function in Cvector. It was then
- inaccessable to functions in the Cmatrix class, unless I declared that class a
- friend. I would have to modify CVECTOR.HPP whenever I decided there was
- another use for the function as constituted.
- C++ is best suited to bottom-up programming. Bottom-up programming entails
- building tools, such as reusable routines that are often based on less
- sophisticated existing code. Inheritance is ideal for this. On the other hand,
- there seems to me to be little value to inheritance for the first shot at
- code, since you can build in the structure you need. Inheritance is useful
- only when you want to reuse a class that isn't quite up to snuff.
-
-
- Comments On Zortech And Turbo C++
-
-
- The Zortech compiler comes with two programs to do the second pass. If the
- first version runs out of memory doing the compilation, you try the second. If
- that one runs out of memory, you are out of luck. It's also possible to run
- out of memory on the first pass. These features enforce code modularity,
- because the first version did not tolerate even moderately sized programs.
- Zortech and other C++ compilers use name "mangling,". This means that the
- compiler sees a function like this:
- FILE *OpenFile(const char *FileName,
- const char *IoMode=0);
- the symbol name that it actually places in the object code might be something
- like OpenFile___NpCcT1. The funny characters at the end are an encoding of the
- fact that this function takes two arguments which are both near pointers to
- constant characters.
- The purpose of this is encoding is to allow the linker to detect some kinds of
- type errors. For example, if I referenced the function shown in the previous
- paragraph, but defined it incorrectly as
- FILE *OpenFile(const char
- *FileName);
- then, because functions that accept different types of arguments are given
- different names, the linker would object that the function that was referenced
- had not been defined. Unfortunately, the Zortech linker displays the mangled
- names in its error messages (there is an extra utility that unmangles them as
- an extra step). The Turbo linker automatically unmangles the names in its
- error messages, so you don't have to think about it.
- Turbo C++ has a much snazzier appearence than the previous release of Turbo C.
- This comes at some cost. Before, the F3 function key instantly gave you a
- window for a file to load. Now, you have to wait a few seconds, depending upon
- the speed of your host machine and the clutter of your disk, while it develops
- a directory of your files. You'll have to throw away your old project files
- (*.PRJ) as the format has changed. In fact, so much has changed you'll find
- yourself re-learning how to use the program. (For example, if you are
- mouseless and want to search backwards, you now need to use the tab key to get
- to the option for backward searches, as the return will now begin the search.)
- But don't throw away your old C code or object modules. These may still be
- linked to, either with Zortech's cdecl keyword declaration or with Turbo C's
- extern "C" declaration.
- Both programs generated .EXE files of similar size and speed. Be prepared for
- the additional language complexity to slow compilations compared to straight C
- compilers. That does not imply a runtime overhead, however. In fact, Zortech
- can generate more efficient function call/return code for C++ functions
- because the required function prototype guarantees the number and type of
- function parameters.
- Turbo C++ was pickier than the Zortech, catching a missing void declaration
- for a function which returned nothing. There were also minor differences in
- the languages accepted by the two compilers. Turbo C++ attempts to implement
- the language strictily as defined by AT&T's release 2.0, whereas Zortech has
- some features of AT&T release 2.1.
- For example, the 2.0 definition of C++ did not allow you to define an array of
- class objects unless that class contained a constructor that took no
- arguments. That made sense because the constructor would be getting called
- implicitly in a loop by the compiler to initialize each object in the array --
- there is no way for you to specify arguments to the constructor in that
- situation.
- However, the 2.1 definition of C++ loosened that restriction a little by also
- allowing constructors that have only arguments with default values specified.
- That allows a little more flexibility for the programmer with only a small
- increase in complexity for the compiler. The net result was a constructor
-
- complex ( double reali=0.,
- double imagi=0.)
- that allowed statements like
- complex *head; head =
- new complex[10];
- with Zortech, but Turbo complained that complex::complex() was not defined.
- Because of the speed at which C++ is evolving, minor incompatibilities like
- this are a fact of life for C+ + programmers.
- References
- J. F. Dreitlein & J. R. Sauer, "Spinor Software Tools in C++", Computers in
- Physics, Jan./Feb. 1990, p.64.
- Bruce Eckel, Using C++, N.Y.: Osborne/McGraw-Hill,1989.
- C. L. Lawson, R. J. Hanson, D. R. Kincaid, F. T. Krough, "Algorithm 539: Basic
- Linear Algebra Subprograms for Fortran Use", Collected Algorithms from ACM
- (CALGO), also "ACM Transactions on Mathematical Software", 5,p.324,1979.
- J. Spanier and K. B. Oldham, Handbook of Functions, N.Y.: Hemisphere, 1987.
- Vendors
- Zortech C++
- Zortech Inc.
- 1165 Massachusettes Ave.
- Arlington, MA 02174
- (617) 646-6703
- FAX (617) 643-7969
- (800) 848-8408
- UK (44) 81-316-7777
- Turbo C++ v2.0
- Borland Int'l
- P.O. Box 660001
- Scotts Valley, CA 95067-0001
- (800) 331-0877
- outside USA 408-438-5300
-
- Listing 1
- // complex.hpp
- #include <math.h>
- #include <stdio.h>
-
- class complex {
- protected:
- double x,y;
- public:
- // complex( double xx = 0., double yy = 0.) //create
- // { x=xx;y=yy;}
- complex( double xx , double yy = 0.) //create
- { x=xx;y=yy;}
- complex() //create
- { x=0.;y=0.;}
- inline void operator=(complex rvalue)
- {x=rvalue.x;y=rvalue.y;}
- inline void operator-=(complex rvalue)
- {x-=rvalue.x;y-=rvalue.y;}
- inline void operator+=(complex rvalue)
- {x+=rvalue.x;y+=rvalue.y;}
- inline void operator*=(complex rvalue)
- {
- *this=complex(rvalue.x*x-rvalue.y*y,
- rvalue.x*y+rvalue.y*x);
- //return *this;
- }
- inline void operator*=(double rvalue)
- {
- *this=complex(rvalue*x, rvalue*y);//return *this;
- }
- inline complex operator+(complex rvalue)
-
- {return complex(x+rvalue.x,y+rvalue.y);}
- inline complex operator-(complex rvalue)
- {return complex(x-rvalue.x,y-rvalue.y);}
- inline complex operator-() //unary minus
- {return complex(-x,-y);}
- inline complex operator*(complex rvalue)
- {return complex(
- rvalue.x*x-rvalue.y*y,
- rvalue.x*y+rvalue.y*x);}
- inline friend complex operator/(double dividend,complex divisor)
- { double temp;
- temp=1./(divisor.x*divisor.x+divisor.y*divisor.y);
- return complex((dividend*divisor.x)*temp,
- (-dividend*divisor.y)*temp);
- }
- inline complex operator/(complex divisor)
- {
- double temp;
- temp=1./(divisor.x*divisor.x+divisor.y*divisor.y);
- return complex((divisor.x*x+divisor.y*y)*temp,
- (divisor.x*y-divisor.y*x)*temp);
- }
- inline int operator==(complex rvalue)
- {return (x==rvalue.x && y==rvalue.y);}
- inline double real() {return x;}
- inline double imaginary() {return y;}
- inline complex conjugate()
- {return complex(x,-y);}
- inline friend complex operator*(complex num,double real)
- {return complex(num.x*real,num.y*real);}
- inline friend complex operator*(double real,complex num)
- {return complex(num.x*real,num.y*real);}
- inline friend complex operator+(complex num, double real)
- {return complex(num.x+real,num.y);}
- inline friend complex operator+(double real,complex num)
- {return complex(num.x+real,num.y);}
- inline complex operator+=(double real)
- {return complex(x+=real,y);}
- inline complex operator==(double real)
- {
- return complex(x-=real,y);}
- inline complex operator++()
- {x+=1.;return *this;}
- inline friend complex operator/(complex num, double real)
- {return complex(num.x/real,num.y/real);}
- inline friend complex operator-(complex num,double real)
- {return complex(num.x-real,num.y);}
- inline friend complex operator-(double real,complex num)
- {return complex(real-num.x,-num.y);}
- double abs();
- complex cexp();
- complex clog();
- complex operator^(double expon);
- complex operator^(complex expon);
- friend complex operator^(double base, complex expon);
- void print( char *ahead="",char *behind="");
- complex hurwitz(complex );
- };
-
-
-
- Listing 2
- //#include <stream.hpp> //for Zortech C++
- #include <iostream.h> //for TURBO C++
- #include <math.h>
- #include "complex.hpp"
- #define errorcode -1.e60
- #define ABS(x) ((x)>0.?(x):-(x))
- #define max(a,b) ((a)>(b)?(a):(b))
- #define pi 3.141592653589793238462643383279
-
- void complex::print( char *ahead,char *behind)
- {char *between="";
- if(y>=0.)between="+";
- printf("%s %e%s%e i %s",ahead,x,between,y,behind);}
-
- double complex::abs()
- { return sqrt(x*x+y*y);}
-
- complex complex::cexp()
- {double scale;
- scale= exp(((double)x));
- return complex(scale*cos(y),scale*sin(y));
- }
- complex complex::clog()
- {double mant,arg,mag;
- mant = log((*this).abs());
- arg= atan2(y,x);
- return complex(mant,arg);
- }
-
- complex complex::operator^(double expon)
- {
- complex z;
- z= (*this).clog() *expon;
- return z.cexp();
- }
-
- complex complex::operator^(complex expon)
- {
- complex z;
- z= (*this).clog() *expon;
- return z.cexp();
- }
-
- double Rzeta2( int k)
- // Riemann Zeta function of 2*(k+1) returned
- {double z[6]={1.64493406684822643647,1.08232323371113819152,
- 1.01734306198444913971, 1.00407735619794433938,
- 1.00099457512781808534,1.00024608655330804830};
- if(k<6)return z[k];
- // return 1+ 2^(-2k)+...+ 6^(-2k) in simplified form;
- if(k>=30)return 1.;
- double fk=-((k+1)<<1); double p2=pow(2.,fk);
- return 1.+((p2+pow(3.,fk))*(1.+p2)+pow(5.,fk));
- }
-
- void prep( complex& u, complex& v, complex& f)
- {
-
- f=0.;
- do {
- f+=u^(-v);u++;
- if(v.real()<1.)
- { while(u.real()>2.){u-=1.;f-=u^(-v);}}
- }while(u.real()<=v.real());
- }
-
- #define tolabs 1.e-10
- #define tolrel 1.e-5
-
- int size=80,szused,jused;
-
- complex complex::hurwitz(complex u)
- {
-
- complex b,c,f,t,p,q,*h,hold,tpu,ev;int i,j,k; double diff,tk,z,scale;
- if( *this==1. ((u-*this+(*this).abs())==0.&&
- (*this).imaginary()==0.))
- return complex(errorcode,0.);
- prep(u,*this,f);
- //u.print("modified a=","");f.print(" modifed sum=","\n");
- ev= -(*this);
- j=4;scale=.25;
- jused=j=max(j, (int)(((*this).abs()+.999)*scale));
- if(x<0.)jused=j=0;
- b=u+ ((double)j); c=u;
- for(i=0;i<j;i++){f+= c^ev;c+=1.;}
- //f.print(" modifed sum=","\n");
- if( size%2)size++;//size must be even integer
- h=new complex[size+1] ;
- t=complex(-2.,0.);
- h[0]=1.;k=0;
- tpu=2.*pi*b;
- tpu=1./(tpu*tpu);
- while(k<size){
- tk=k<<1;
- t*=(*this+tk)*(1.-*this-tk)*tpu;
- z=Rzeta2(k);
- k++;
- h[k]=h[k-1]+t*z;
- q=0.;j=k;
- do {
- p=h[j-1];
- if(h[j]==p)
- {
- h[j-1]=-errorcode;
- if(p==-errorcode)h[j-1]=q;
- }
- else h[j-1]= q+1./(h[j]-p);
- q=p;j--;
- }while(j);
- if(!(k%2))
- {
- diff=(hold-h[0]).abs();
- if(diff<tolabs diff<tolrel * h[0].abs())break;
- hold=h[0];
- }
- };
-
- szused=k;
- ev= -(*this);
- tpu= 1.+ev;
- q=-h[0]*(b^tpu)/tpu;
- delete h;
- return f+.5*(b^ev)+q;
- }
-
- main()
- {complex u,v,w;double a,b,c,d;
-
- while(1)
- {
- cout <<" enter v,u" ;
- //scanf("%le%le%le%le",&a,&b,&c,&d);
- cin >> a >> b >> c >> d ;
- if(a==0.&&b==0.&&c==0.&&d==0.)break;
- u=complex(c,d);v=complex(a,b);
- w=v.hurwitz(u);
- w.print(" Hurwitz=","\n");
- cout << " max k used=" << szused << ", J used: " << jused << "\n" ;
- };
- }
-
-
- Listing 3
- // Cvector.hpp
- #include <stdlib.h>
-
- #include "complex.hpp"
- static complex *present;
-
- class Cvector {
- protected:
- public:
- int size,base;
- complex *head;
- Cvector(int s=1,int b=0,complex initvalue= complex(0.,0.))
- //constructor
- { head= new complex[s];base=b;
- for(size=0,present=head;size<s;
- size++,present++) *present = initvalue;
- size=s;
- printf(" Cvector built\n");
- }
- Cvector( Cvector&); //copy
- ~Cvector() //destructor
- {
- delete head; printf(" Cvector axed\n");}
- void operator=( Cvector& rhs);
- complex operator*(Cvector& rvalue);//dot product
- inline int length()
- {return size;}
- inline complex& elemnt( int i)
- {return head[i-base];}
- inline void setelemnt( int i, complex& value)
- {head[i-base]=value;}
- void check(int);
- complex& element( int i);
-
- void setelement(int i, complex& value);
- };
-
-
- Listing 4
- //matrix package
-
- #include "cvector.hpp"
-
- class Cmatrix {
-
- private:
- void init(int row=1,int col=1,int b=0);
- public:
- complex** m;
- int rowkt,colkt,base;
-
- Cmatrix(int rowkt,int colkt,int b)
- { init( rowkt, colkt, b);} //constructor
- Cmatrix() {init();} // "
- Cmatrix( Cmatrix&); // init
- ~Cmatrix(); // destructor
- void operator=(Cmatrix& );
- inline complex& elemnt(int i,int j)
- {return m[i-base] [j-base];}
- void check(int,int);
- complex& element(int,int);
- inline void setelemnt(int i,int j,complex value)
- {m[i][j]=value; }
- void setelement(int,int, complex);
- friend Cmatrix operator*(Cmatrix&,Cmatrix&); //Mat*Mat
- friend Cvector operator*(Cmatrix&,Cvector&); //Mat*Vector
- };
-
-
- Listing 5
- #include <stdlib.h>
- #include "cmatrix.hpp"
-
- error(char* msg, int index,int s)
- {
- printf("%s%d %d\n",msg,index,s);
- exit(1);
- }
-
- void Cvector::check( int index)
- {int loc;
- loc=index-(*this).base;
- if (loc<0)
- error(" Cvector error: index-base<0",index,(*this).base);
- if (loc>= (*this).size)
- error(" Cvector error: index too large",index,(*this).size);
- }
-
- void Cmatrix::check( int i, int j)
- {int loc;
- loc=i-(*this).base;
- if (loc<0)
- error(" Cmatrix error: row index-base<0",i,(*this).base);
-
- if (loc>= (*this).rowkt)
- error(" Cmatrix error: row index too large",i,(*this).rowkt);
-
- loc=j-(*this).base;
- if (loc<0)
- error(" Cmatrix error: column index-base<0",j,(*this).base);
- if (loc>= (*this).colkt)
- error(" Cmatrix error: column index too large",j,(*this).colkt);
-
- }
-
- void Cmatrix::init(int row, int col, int b)
- {
- if(row<1col<1)error(" matrix needs at least one row/col ",row,col);
- rowkt=row;colkt=col;base=b;
- m= new complex *[rowkt];
- for(int j=0;j<rowkt;j++)m[j]= new complex[colkt];
- complex zero;complex one(1.,0.);
- //initialize to identity matrix.
- for(int i=0;i<rowkt;i++){for(j=0;j<colkt;j++)m[i][j]=zero;
- m[i][i]= one;// omit this line for init. to zero matrix
- }
- }
-
- Cmatrix::Cmatrix(Cmatrix& a)
- { init(a.rowkt,a.colkt,a.base);
- for(int i=0;i<a.rowkt;i++)for(int j=0;j<a.colkt;j++)m[i][j]=a.m[i][j];
- }
-
- Cmatrix::~Cmatrix()
- {for(int i=0;i<rowkt;i++)delete m[i];
- delete m;
- }
-
- complex& Cvector::element(int i)
- {
- check(i);return head[i-base];
- }
-
- void Cvector::setelement( int i, complex& value)
- {
- check(i);
- head[i]=value;
- }
- complex& Cmatrix::element(int i,int j)
- {
- check(i,j);return m[i-base][j-base];
- }
-
- void Cmatrix::setelement( int i, int j, complex value)
-
- {
- check(i,j);m[i][j]=value;
- }
-
- void Cmatrix::operator=(Cmatrix& a)
- {
- if(this==&a)return;
- for(int i=0;i<rowkt;i++)delete m[i];
-
- delete m;
- init(a.rowkt,a.colkt,a.base);
- for(i=0;i<a.rowkt;i++)for(int j=0;j<a.colkt;j++)m[i][j]=a.m[i][j];
- }
-
- Cvector::Cvector( Cvector& orig) //copy
- {
- size=orig.size;base=orig.base;head=new complex[orig.size];
- for(int i=0;i<size;i++)head[i]=orig.head[i];
- }
-
- Cmatrix operator*(Cmatrix&a, Cmatrix& b)
- {
- int i,j,k;complex zero;
- if(a.colkt!=b.rowkt)
- error(" cannot multiply matrices colkt,rowkt ",a.colkt,b.rowkt);
- Cmatrix prod(a.rowkt,b.colkt,a.base);
- for(i=0;i<a.rowkt;i++)for(j=0;j<b.colkt;j++)
- {prod.m[i][j] =zero;
- for(k=0;k<a.colkt;k++)prod.m[i][j] += a.m[i][k]*b.m[k][j];
- }
- return prod;
- }
-
- Cvector operator*(Cmatrix& a, Cvector& b)
- {
- int k;complex zero;
- if(a.colkt!=b.size)error(" cannot mult. matrix,vector ",a.colkt,b.size);
- Cvector prod(b.size,b.base,zero);
- for(int i=0;i<a.rowkt;i++)
- {prod.head[i]=zero;
- for(k=0;k<a.colkt;k++)prod.head[i] += (a.m[i][k])*(b.head[k]);
- }
- return prod;
- }
-
- complex Cvector::operator*(Cvector& rvalue) // dot product
- {int i; complex *element,*relement; complex sum(0.,0.);// sum
- if(rvalue.size!= size)
- error(" dot product unequal length vectors ",size,rvalue.size);
- for(i=0,element=head,relement=rvalue.head;i< size;
- i++,element++,relement++)
- sum += *element * *relement;
- return sum;
- }
-
- void Cvector::operator=( Cvector& rhs)
- {
- if( this== &rhs)return;
- if(size!=rhs.size)
- {
- delete head;
- head= new complex[rhs.size];
- }
- for(int j=0;j<size;j++){head[j]=rhs.head[j];}
- }
-
- main()
- {
-
- complex a,b(1.,0.),c(0.,1.); double dot; int i,j;
- printf(" size of complex=%d\n",sizeof(complex));
- a=b+c;
- a.print("summand=","\n");
- printf(" magnitude=%e\n", a.abs());
- b=a/c;
-
- b.print("quotient=","\n");
- a=b.cexp();
- a.print(" exponential=","\n");
- a= complex(1.,0.);b=complex(3.,2.);
- printf(" size of Cvector=%d\n",sizeof(Cvector));
- //
- {Cvector A(4,0,a);Cvector B(4,0,b);Cvector D(4,0,b);
- for(j=0;j<B.size;j++) B.element(j).print(" B=","\n");
- a= A*B;
- B=A;
- for(j=0;j<B.size;j++) B.element(j).print(" B=","\n");
- a.print(" dot product=","\n");
- {Cmatrix m(4,4,0),n(4,4,0),o(4,4,0);
- c=complex(0.,1.);
- for (i=0;i<4;i++)
- for(j=0;j<4;j++){
- m.m[i] [j].print(" matrix element=","\n");}
- for (i=0;i<4;i++)
- for(j=0;j<4;j++){
- n.setelement( i,j, complex((double)(i+j),0.));
- n.m[i] [j].print(" matrix element=","\n");}
- D = (m * A);
- for(j=0;j<B.size;j++) B.element(j).print(" B=","\n");
- B = (m * A);
- for(j=0;j<B.size;j++) B.element(j).print(" B=","\n");
- o= m * n;
-
- for (i=0;i<4;i++)
- for(j=0;j<4;j++){
- o.m[i] [j].print(" matrix element=","\n");}
- printf(" end of inner block\n");
- }
- printf(" end of block\n");
- }
- printf(" end of program\n");
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Software Engineering in C
-
-
- Robert E. Cady
-
-
- Bob Cady holds bachelor's degrees in Computer Science, Electronics Technology,
- and Business Management and has been programming for 13 years. He is president
- of a software company developing communications programs for DOS and Windows.
- He may be contacted at Tethys Software Co, 6651 E. Indian River Rd., Suite
- 138, Va Beach, VA 23464-3441.
-
-
- Despite the title, Software Engineering in C is not about software
- engineering. The authors describe it in the preface as a textbook for
- beginning and intermediate C programmers. It serves that purpose well and
- emphasizes good programming style.
- This fast-paced book is organized into 12 chapters and six appendices. Every
- chapter concludes with exercises that test understanding of the material
- covered. It does not assume any particular operating system, hardware, or
- compiler.
- Chapter one briefly introduces high-level programming languages and the
- history of C. Chapter two explains the fundamentals of C programming: the
- preprocessor, the #include and #define directives, and the
- edit/compile/link/execute cycle. It also covers C functions in general and
- main() and printf() in particular, reserved keywords, variable naming,
- expressions, and assignments. Chapter three is an extensive discussion of
- scalar data types, including pointers and typedef. Notable are the sections on
- implicit and explicit conversions, enumeration types, and the mixing of data
- types in expressions.
- Chapters four through 11 cover the real meat of C programming. The authors use
- flow charts and formal syntax charts. Unfortunately, they don't describe
- either of these types of charts, so if you aren't already familiar with them,
- they may be more confusing than helpful. Chapter four introduces control flow,
- and discusses conditional branching, looping, the switch statement, and gotos.
- It also includes a section on infinite loops. Chapter five, "Operators and
- Expressions," covers precedence of operators, relational operators, and
- logical operators, but only briefly explains bitwise operators.
- Chapter six contains some of the best discussions of arrays and pointers I
- have seen. It gives good examples of initializing arrays, passing them to
- functions, and pointer operations on them. It also effectively covers pointer
- arithmetic and using arrays of pointers. Chapter seven, one of the most
- valuable chapters in this book, discusses storage classes. This chapter
- clearly describes scope and duration of variables.
- Chapter eight, "Structures and Unions," is the next most valuable chapter.
- This chapter clearly explains how to set up and use unions and nested
- structures. Chapter nine is a rigorous look at functions, pointers to
- functions, recursion, and prototyping. However, the discussion on calling
- functions using pointers is shallow. Because this book is not intended for
- advanced users, I don't consider this a major deficiency.
- Chapter 10 adequately discusses the preprocessor, with a good comparison of
- macros and functions. Chapter 11 extensively discusses file input and output.
- It explains buffered and unbuffered I/O, differentiating between sequential
- and random access methods.
- Chapter 12 is the only chapter that deals with software engineering, and it
- barely scratches the surface of this important topic. It covers product
- specification, software design, project management, software production tools,
- debugging, testing, performance analysis, and documentation in only 30 pages.
- By way of example, it includes the functional specification and project plan
- for a C interpreter. The book includes source code for the interpreter in an
- appendix.
- Appendix A is a comprehensive overview of the ANSI C standard, including a
- listing of all functions in the runtime library. Appendix B contains the
- formal syntax charts for the C language. Appendix C is a list of numerical and
- translation limits. Appendix D lists the differences between K&R and ANSI C.
- Appendix E is a list of reserved names. Appendix F is a listing of the source
- code for the C interpreter referred to in Chapter 12. The source code is also
- available on diskette. Appendix G is the obligatory listing of ASCII character
- codes (up to Ox7f). This book was copyrighted in 1988. There have been
- numerous changes to the ANSI standard since then, so you can't rely on it as
- your reference to the standard.
- This book is written in a clear, friendly, and easy-to-read style. The
- sections in each chapter contain enough examples to adequately reinforce the
- subject matter. Each chapter contains sidebars containing either bug alerts or
- notes on ANSI implementation. The bug alerts include the usual errors, such as
- using an assignment operator (=) when a test for equality (==) is intended, or
- vice versa. It also highlights more subtle errors. For example, the expression
- if ((a < b) && (c == d++))
- is discussed. It is certainly possible that a programmer would intend that d
- be incremented only if a is less than b, but it seems more likely that d
- should always be incremented. There are quite a few of these helpful alerts
- throughout the book.
-
-
- Summary
-
-
- Although the authors intended this book for beginning and intermediate C
- programmers, I don't think it is suitable for beginning programmers. It is a
- very fast-paced book that implicitly expects the reader to have a good
- understanding of some other high-level structured programming language, such
- as Pascal or COBOL. As an advanced C programmer, I have found this book to be
- most valuable as a reference. In fact, it has replaced five C books I used to
- keep on the shelf above my programming desk. The replaced books include K&R
- (first edition), Plum's Reliable Data Structures In C, and two bookds titled
- Advanced C.
- I like this book and use it a lot. I'm not programming 12 hours a day as I
- used to, so I rely heavily on a good reference, especially when working under
- a deadline at a client site. Software Engineering in C is that reference and I
- highly recommend it. If you are going to have just a couple of books on C,
- include this one on your list.
- Software Engineering in C
- Peter A. Darnell and Philip E. Margolis
- Springer-Verlag
- $32.00, 612 pages
- ISBN 0-387-96574-2
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Editor's Forum
- Yes, Virginia, there really is a Standard C.
- Tom Plum encouraged me to start using that term several years ago. He was
- looking ahead to a day when both ANSI and ISO would complete their work on a C
- standard. Like many of us, he was and remains fiercely dedicated to the notion
- that the two standards should be identical. The last thing we wanted was an
- endless debate on the relative merits, and the relative importance, of ANSI C
- versus ISO C.
- Just to get people in the habit, Tom argued, start using the term Standard C.
- Call it ANSI C where you must, but go easy on any nationalistic sentiments.
- Plan ahead.
- That, of course, is why my monthly column in The C Users Journal has been
- called "Standard C" from the outset, over two years ago. That is why Jim
- Brodie and I called our comprehensive reference, Standard C, even though the
- state of both ANSI and ISO standards were uncertain the day we froze copy.
- That is why I cheer whenever I see others adopt the term as a matter of
- course.
- I am happy to pass on some good news from Dave Prosser. Dave is the Editor of
- both standards. He has been wrestling for months, in his copious spare time,
- with the formatting requirements imposed by ISO. (The two standards may be
- identical semantically, but the separate organizations have in the past
- required different layouts, and sometimes even different spellings of words.)
- Dave called me the day before Thanksgiving to report that the final version of
- the ISO C standard was on its way to Geneva.
- Unless we on WG14 miss a beat, we should have an official ISO standard by the
- end of 1990. Standard C has thus become a fitting term, one that rolls off the
- tnogue more smoothly than ANSI/ISO C. Tom Plum, and quite a few others, are
- getting their wish.
- The battle is far from over. NIST, the government agency in charge of
- specifying language standards for government procurement, has its oar in the
- water. They have already proposed additional requirements on conforming
- translators, typically in the area of error reporting. As far as I know, they
- haven't changed the underlying language. At least not yet. And X3J16 is busy
- standardizing C+ +. Many of us have a concern that this newer language not
- deviate arbitrarily from its C heritage.
- Extensions to C itself are now under active consideration within WG14. There
- is a growing concensus that this is the proper venue for exploring proposed
- changes to C, since most of the changes affect the international community.
- C is not Latin. So long as it is alive and growing, it will change. At this
- moment in time, however, it is refreshing to know that there really is a
- Standard C.
- P. J. Plauger
- Editor
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- New Products
-
-
- Industry-Related News & Announcements
-
-
-
-
- ydb Symbolic Debugger For yacc Grammers And Parsers
-
-
- Bloomsbury Software Group is now shipping the ydb symbolic debugger for yacc
- grammers and parsers. ydb is a grammer development tool offering interactive
- environments for grammar debugging and parser generation on UNIX systems. It
- is 100 percent backward compatible with yacc, the standard UNIX parser
- generator, and so can be used for debugging yacc grammars, or for creating
- parsers more flexible than those generated by yacc.
- ydb provides a set of tools for producing correct, conflict-free grammars at
- translate time. ydb also offers users complete debugging control of an
- operating parser at runtime, including the ability to trace parser actions and
- set breakpoints at particular rules or other points in the parse.
- ydb is available immediately for Sun 3, Sun 4, and DECstation computers, and
- is being ported to others, including Hewlett-Packard workstations. ydb offers
- X windows, sunView and ASCII terminal interfaces.
- Prices begin at $1,250 for a single CPU license. The company offers academic
- discounts, quantity discounts and site licenses. For more information, contact
- Bloomsbury Software Group, P.O. Box 390018, Mountain View, CA 94039, (415)
- 964-3486.
-
-
- Intermetrics Provides Debugging Environment For Motorola 68332
-
-
- Intermetrics Microsystems Software has released RMXDB 5.0, a ROM
- monitor-based, C source-level cross debugger for Motorola's 68332 processor.
- RMXDB 5.0 integrates the source-level debugging features of XDB 5.0 (as
- source-level cross debugger) with a low-level target monitor program.
- RMXDB 5.0 is compatible with Motorola's 68332 development system. The
- ROM-based monitor portion of RMXDB is pre-configured to reside directly on the
- EEPROMs located on the 68332 EVS, and communicate target information to the
- RMXDB interface on the host computer.
- Features of RMXDB 5.0 include the ability to display source code, registers,
- and stack information, set software breakpoints, single step at the C or
- assembly level, monitor and modify data, define macros and aliases, record and
- playback debugging sessions simulate target I/O, and access pop-up windows for
- status information and online help.
- RMXDB is integrated with the Inter-Tools set of C cross compilers, cross
- assemblers, and programming utilities for writing ROMable, reentrant code for
- the 68332. RMXDB can be configured for almost any hardware environment with a
- serial port. Intermetrics provides an installation kit to help configure the
- ROM monitor for the user's board. This kit includes pre-written driver
- programs, and configuration examples for a variety of off-the-shelf single
- board computers and common USART boards.
- Prices for the RMXDB 5.0 begin at $2,500. For more information, contact
- Intermetrics Microsystems Software, 733 Concord Ave., Cambridge, MA
- 02138-1002, (617) 661-0072; FAX (6I 7) 868-2843.
-
-
- Microsoft Releases New Programming Tools
-
-
- Microsoft Corporation has made available three programming tools: the
- Microsoft Professional Advisor Library, the Microsoft C Developers Toolkit,
- and a maintenance release to Microsoft C Professional Development System.
- The Professional Advisor Library provides software developers with the core
- functionality required to add context-sensitive, hypertext-based online help
- to their DOS and OS/2 applications. The library offers extensive
- cross-referencing and cross-linking capabilities with highly compressed data
- for minimal hard disk usage. The library provides routines for base file
- management, topic look-up, context maneuvering, support for text attributes,
- and utility routines for handling multiple help files.
- The Professional Development System integrates the process of editing,
- building, and debugging an application. The C Developers Toolkit software and
- documentation provide the details necessary to write complementary products
- for these systems tools, including the Source Browser, Code-View, and
- Programmer's WorkBench. By integrating their tools, add-on tool vendors can
- simplify their users' development projects.
- The toolkit provides documentation on the formats for the CoveView debugging
- object module, extended executable debug information, symbolic information,
- and incremental linker.
- The C Professional Development System v6.0 provides developers with an
- open-architecture integrated system. It features globally optimizing C
- compiler, Source Browser, Programmer's WorkBench development environment, and
- CodeView debugger, as well as other tools.
- The Professional Advisor Library sells for $49.95, the C Developer's Toolkit
- sells for $29.95, and the C Professional Development System v6.0 sells for
- $12.95. For more information, contact Microsoft Corporation, One Microsoft
- Way, Redmond, WA 98052-6399, (206) 882-8080; FAX (206) 883-8101.
-
-
- WinSoft Releases Data Validating Screen Library For Windows 3.0
-
-
- WinSoft has released the commercial version of Instant Windows, a software
- tool for developing portable data-oriented applications for Windows 3.0 and
- DOS. Instant Windows is a true data validation oriented C library for Windows
- 3.0. It interfaces easliy to SQL server, Novell BTrieve and XQL, Oracle,
- Informix, Sybase, Xdb, dbVista, CTree, WinTrieve, CTrieve, CIndex, 3270/HLL
- API, and other database and communciations systems.
- Instant Windows offers data validation, edits, and customization. Standard
- validations include number/string controls, a variety of picture strings, and
- range checks. Instant Windows generates automatic bug-free C code for complete
- user interface. It offers a C function library that lets the programmer
- develop the bulk of his application with only three major functions.
- Instant Windows prices start at $249 for MS-DOS and $995 for Windows 3.0. For
- more information, contact WinSoft, 1016 E. El Camino Real, Suite 216,
- Sunnyvale, CA 94087, (415) 324-9552.
-
-
- HP Unveils Programming Advancements
-
-
- Hewlett-Packard has introduced three object-oriented programming advancements
- for HP 9000 and HP Apollo computer platforms: C++ v2.1 compiler on the HP-UX
- operating system; C++ Developer on Domain operating system, and Domain/C++
- v2.1.
- These advancements add improved compile-time performance, simplified class
- construction, improved code modification and support for C++ v2.1 from AT&T.
- The HP-UX and Domain operating systems comply with AT&T's UNIX system.
- Also available form HP is a true C++ compiler based on AT&T C++ v2.1 on the
- HP-UX operating system that generates native code on HP 9000 systems. This
- compiler generates object code directly from C++ source code.
- HP's C++ Developer is a class construction and browsing tool. It is a
- standalone X Window System that provides a graphical representation of the C++
- class-in-heritance hierarchy. The C++ Developer allows users to browse class
- definition and member-function source code graphically, add and modify classes
- and inheritance hierarchies, generate source-code templates and diagnose and
- correct errors automatically.
- For more information, contact Hewlett-Packard Company Inquiries, 19310
- Pruneridge Ave., Cupertino, CA 95014, (800) 752-0900.
-
-
- Borland Offers Upgrade To Microsoft C Users
-
-
-
- Borland International is now offering users of Microsoft C an upgrade to
- Borland's Turbo C++ Professional for $149.95. Customers may obtain the Turbo
- C++ Professional competitive upgrade through participating computer dealers or
- direct from Borland. The offer is valid for owners of Microsoft C or any
- PC-based C or C++ compiler through January 31, 1991 in the United States or
- Canada.
- Turbo C++ Professional is a development environment that lets programmers add
- object-oriented programming to their skill set and more effectively tackle
- programming projects. Turbo C++ Professional contains Turbo C++, Turbo
- Debugger, Turbo Profiler, and Turbo Assembler, and it comes with nine manuals.
- International versions of Turbo C++ Professional and Turbo C++ are available
- in French, German, and Italian. A Japanese version will be available in
- January 1991.
- For more information, contact Borland International, 1800 Green Hills Road,
- Scotts Valley, CA 95066-0001. To order an upgrade, call (800) 331-0877.
-
-
- Design Tool Now Generates C++
-
-
- Caset Corporation has enhanced the Hierarchical Object-Oriented Design (HOOD)
- Toolset to generate C++ code, in addition to ADA, to implement object-oriented
- designs. The HOOD toolset is an interactive, workstation-based tool supporting
- multi-user design teams.
- The toolkit organizes design information in a design database facilitating
- iterative design refinement. The toolkit generates documentation and
- implementation code in ADA and now in C++. Design information can be
- transferred between the ADA and C++ toolset variants.
- The HOOD object management system captures design details for later
- representation as C++ code fragments or ADA modules, so developers can
- implement each HOOD object as an instance of its own C++ class. Standard
- system specification packages and their own interdependencies are recorded as
- environmental objects. Third party packages and re-usable code modules are
- represented as class objects.
- The HOOD C++ and ADA mappings are separately available options. Either
- implementation language may be initially specified, the other language may be
- purchased as an extension. SUN, DEC, HP, and additional UNIX/VMS workstations
- are currently supported.
- For more information, contact Caset Corporation, 33751 Connemara Drive, P.O.
- Box 939, San Juan Capistrano, CA 92693, (714) 496-8670; FAX (714) 661-5463.
-
-
- SDE Introduces OOSD/C++
-
-
- Interactive Development Environments has added Object-Oriented Structured
- Design (OOSD)/C++ to its Software through Pictures family of CASE products.
- OOSD/C++ includes a graphical design editor that automates the standard OOSD
- notation extended for C++.
- The product's features include C++-specific drawing rules, which minimize
- errors, and the C++ Reuse Library and Browser, which reinforce the reuse
- capabilities of object-oriented languages while still in the design phase.
- OOSD/C++ also includes data modeling editors, a central repository, document
- preparation, and version control. An automated training tool that provides
- drawing support for the OOSD notation extended for C++ is available as part of
- the recently introduced "Development Methods for Migrating from C to C++ using
- OOSD" course. OOSD/C++ will be available as part of an open solution, called
- the C++ Development Environment.
- OOSD/C++ includes a graphical editor, which automates OOSD for C++, two data
- modeling editors, a multi-user repository, the document preparation system,
- and version control. The basic OOSD notation has been extended to include
- classes, member functions, templates, global and local scoping, single and
- multiple inheritance, as well as public, private and protected relationships.
- The product allows users to browse class hierarchies in the C++ Reuse Library,
- which increases quality and productivity by reinforcing the reuse capablities
- of object-oriented languages while still in the design phase. In the future,
- the library will be pre-populated with extensible class libraries. Another key
- feature of OOSD/C++ is the C++ Guidance System, which minimizes errors by
- enforcing C++-specific syntax rules regarding the interaction between C++
- components, such as assembly of classes, connections between objects, and
- exportation of objects.
- For more information, contact Interactive Development Environments, 595 Market
- St., 10th Floor, San Francisco, CA 94105, (415) 543-0900; FAX (415) 543-3716.
-
-
- FairCom Enhances File Handler
-
-
- FairCom has released c-tree Plus, a new file management and data server
- product that enhances and extends the c-tree file handler, FairCom's file
- management toolkit.
- Using c-tree Plus, programmers can build applications for more than 100
- environments, receive full portability offered by the C programming
- environment. With these tools, developers can use either high-level index
- sequential-like access methods, or low-level data management functions. Both
- methods offer advanced features, such as transaction processing, ANSI-standard
- SOL support, resource records, superfiles and batch operations.
- The c-tree Plus file handler sells for $595. Current users of c-tree can
- upgrade for $200. For more information, contact FairCom, 4006 West Broadway,
- Columbia, MO 65203, (800) 234-8180; FAX (314) 445-9698.
-
-
- Softaid Introduces Download Package For In-Circuit Emulators
-
-
- Softaid has introduced a high-speed download package for its line of
- in-circuit emulators. The Fiber Optics Link transfers the user's code to the
- emulator at more than 250,000 bytes per second.
- The Fiber Optics Link replaces RS-232 with an ultra high-speed serial
- protocol. All communications goes over the fiber. This link transfers data in
- excess of 250,000 bytes per second; a one million byte program will download
- in only four seconds.
- The Fiber Optics Link includes a five-meter long duplex fiber cable that
- connects the emulator to the PC. A half slot controller board for any PC or
- compatible is included. The link costs $1,200. For more information, contact
- Softaid, 8930 Route 108, Columbia, MD 21045, (301) 964-8455; FAX (301)
- 596-1852.
-
-
- SparcStation 1 Now Hosts C Executive O/S
-
-
- JMI's C Executive operating system for the Sun Microsystems SPARC has been
- repackaged to be hosted on SPARCstation 1. The SPARC is a high-performance
- RISC processor. The SPARC-based workstation can be used to develop
- applications before downloading to a SPARC target board.
- C Executive is a real-time, multitasking operating system kernel used in
- embedded control applications. The new SPARC version of the kernel allows
- current C Executive customers using CISC microprocessors to move their
- applications to the SPARC. The original SPARC port of C Executive was
- accomplished using a SPARC cross compiler on the Sun 2 workstation. The new
- version was produced using native C compiler, assembler, and linker running on
- the SPARCstation 1 under SunOS.
- The new SPARC version also offers an optional file system, CE-DOSFILE, and a
- system debugger, CE-/VIEW. CE-DOSFILE replicates the DOS 8086 file structure
- on external media, allowing SPARC-based embedded systems to read and write DOS
- diskettes online.
- For more information, contact JMI Software Consultants, 904 Sheble Lane,
- Spring House, PA 19477, (215) 628-0846.
-
-
- Book Shows How To Build Reusable Software
-
-
- Prentice Hall has published A C++ Tool Kit by Jonathon Shapiro. In his book,
- Shapiro discusses how object-oriented languages can help with reusing
- software, and he provides a reusable tool in each chapter taken from his own
- programming projects. Divided into four parts, the book covers software reuse
- and object-oriented languages; an overview of C++ implementation details such
- as tuning performance and memory allocation for faster and more efficient
- applications.
- To order this book, contact Prentice Hall, Order Department, 200 Old Tappan
- Rd, Old Tappan, NJ 07675, (201) 767-5937.
-
-
-
- Companies Join C++ Reseller Alliance
-
-
- Sixteen companies have joined the C++ Reseller Alliance, formed by AT&T's UNIX
- System Laboratories, to promote products based on AT&T's C++ programming
- language and libraries, and to share information on using C++ in large
- software development projects.
- The C++ Reseller Alliance will share with its members information about coming
- enhancements to the C++ language and libraries from USL. Membership in The C++
- Reseller Alliance is open to any hardware or software vendor who licenses and
- distributes the AT&T USL C++ Language System. No membership fees are required.
- The group will meet two or three times a year, coincident with significant C++
- industry events.
- For more information, contact Paul Fillinich, C++ Product Manager, UNIX System
- Laboratories, at (201) 580-4363.
-
-
- Expanded Memory Support Enhances Text Editor
-
-
- American Cybernetics has announced version 5.0 of Multi-Edit. The text editor
- offers expanded memory support. Users can select either an SAA (windows-style)
- interface or a Classic (PC-style) interface from a long list of setup options.
- Features include seamless mouse support throughout the editor environment, a
- user's menu for frequently executed macros, programs, and text files; a
- keystroke macro manager to simplify and organize unlimited on-the-fly macros,
- expanded online hypertext help; and a 380-page User's Guide and Macro Lanugage
- Reference Guide.
- For more information, contact American Cybernetics, 455 S. 48th St., Suite
- I07, Tempe, AZ 85281, (602) 968-1945; FAX (602) 966-1654.
-
-
- File Shuttle Utility
-
-
- GetC Software has introduced File Shuttle Xpress 5.0. This File Shuttle
- utility integrates inter-computer file transfer with file management
- capabilities for users running any combination of Windows 3.0, DOS, OS/2 in
- its DOS compatibility box and networks.
- Version 5.0 requires 256K RAM for the DOS version, one parallel or serial port
- on each computer and DOS 2.0 or later. The Windows 3.0 version of the program
- requires that Windows 3.0 be installed with the necessary system requirements
- for that environment.
- File Shuttle Xpress 5.0 sells for $139.95. For more information, contact GetC
- Software, I280 Seymour St., 2nd Floor, Vancouver, B.C. V6B 3N9, (604)
- 684-3230; FAX (604) 689-1401.
-
-
- Expert Systems Module Accelerates Software Development
-
-
- Integrated Systems has introduced a knowledge-based, real-time expert systems
- module. RT/Expert automates and accelerates the development of real-time
- software that incorporates rule-based logic.
- RT/Expert gives users an elegant way to describe a collection of IF-THEN-ELSE
- rules. These rules are effective for diagnostics, monitoring, alarm filtering,
- and mode selection applications.
- For more information, contact Integrated Systems, 2500 Mission College Blvd.,
- Santa Clara, CA 95054-1215, (408) 980-1500; FAX (408) 980-0400.
-
-
- SSC Offers Command Summary For UNIX System V
-
-
- Specialized System Consultants is offering an 80-page command summary for UNIX
- System V.4 commands. System V.4 is the merging of AT&T System 5 releases with
- BSD features.
- SSC's UNIX System V.4 pocket-size reference details command syntax and
- describes the options available for each command. A comprehensive summary of
- the nawk command and a five-page summary of the System V shell are included.
- The summary also includes expanded sections on ed, sdb, sed, and telnet.
- The booklet is priced at $8. For more information, contact Specialized System
- Consultants, P.O. Box 55549, Seattle, WA 98155, (206) 527-3385; FAX (206)
- 527-2806.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- New Releases
-
-
- Updates
-
-
-
-
- CUG329 UNIX Tools for PC
-
-
- Henri de Feraudy has updated his string substitution utility, csubst. This
- update includes minor bug fixes.
-
-
- CUG330 CTask
-
-
- Thomas Wagner (Germany) has updated his multitasking routines, CTask. This
- version 2.2 release includes bug fixes, some internal changes, and addition of
- new functions.
-
-
- New Releases
-
-
-
-
- CUG334 GNUPLOT
-
-
- Written by Thomas Williams, Colin Kelley, modified by Russell Lang, Dave Kotz,
- John Campbell, and submitted by Henri de Feraudy (France), GNUPLOT v2.0 is a
- command-driven interactive function plotting program. By typing commands
- interactively or loading a text file that contains commands, user can draw
- graphs or plot data points on screen in a given graphics mode or printer using
- a given printer driver. GNUPLOT provides a set of commands: loading/saving
- command file, plotting a function(built-in or user-defined) or data files,
- printing a title, label or arrow on a graph, clipping data points, specifying
- graphics mode (CGA, EGA, VGA if PC), line style, grid, ranges, offset, scaling
- size, sampling rate, polar/rectangular coordinate, turning on/off auto-axis
- scaling or auto-tic marks, output redirection, online help, and escaping to
- shell.
- Built-in mathmatical functions are the same as the corresponding function in
- UNIX math library, except that all functions accept integer, real, and complex
- arguments. The sgn function is also supported as in BASIC.
- GNUPLOT supports the following graphics drivers: AED 512, AED 767, BBN
- BitGraph, Roland DXY800A, EEPIC, Epson LX-800, Fig, HP2623, HP2648, HP75xx,
- HPGL, IBM Proprinter, Imagen, Iris4D, Kermit-MS, LaTeX, NEX CP6 pinwriter,
- PostScript, QMS QUIC, ReGis (VT125 and VT2xx), Selanar, Tek 401x, Vectrix 384,
- and UNIXplot. For the PC version, it supports IBM CGA, EGA, MCGA, VGA,
- Hercules, ATT 6300, and Corona 325 graphics.
- The disk includes a complete set of C source files for the program and
- graphics drivers, makefile for UNIX, Microsoft C and Turbo C, documentation,
- demo command files (Fig 1 - 4), and an MS-DOS executable file (compiled under
- Turbo C). The program is compiled under UNIX, VMS, MS-DOS (using Microsoft C
- or Turbo C).
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- We Have Mail
- Mr. Ward:
- I feel obligated to respond to Mr. Curren's letter (CUJ, December 1990)
- criticizing you for including the SoftC Database Library in Volume 326 of the
- CUG library.
- Mr. Curren was upset because he believed that our licenese agreement demanded
- a royalty payment for each copy of his application he sold. This is simply not
- the case! We try to make this clear in the "Product Code and Derivative Works"
- section of the license agreement with the following statement:
- "The Customer may reproduce and distribute application programs created using
- the Product without additional licenses or fees."
- I think this plainly states our position that we do not expect any royalties
- from our users. We do restrict the ways in which the library alone (not as
- part of a derivative product) can be used. Essentially we say that the library
- is to be used like a book. Perhaps Mr. Curren made the mistake of also
- extending this restriction to his applications.
- We think our support policies clearly reflect our user-orientation. Fixes for
- all reported bugs are available directly from us at no charge. Prior to each
- release our library is subjected to several thousand tests (1 megabyte of
- source consisting of 195 test files). We do our best to ship bug-free
- software.
- It's unfortunate that Mr. Curren let the licensing issue keep him from "test
- driving" our product. The combination of our product with the CXL library
- (Vol. 278) has proven to be extremely powerful and in many ways more flexible
- than our competitors'. Many of our users have returned competing products
- after trying our library.
- It is indeed unfortunate that Mr. Curren never sought to contact us. We would
- have been (and still would be) happy to provide explanations for any questions
- he may have had and even supply a free shareware version of the library (as we
- have done for many other software professonals who have called with
- questions).
- Finally, I am curious why you printed Mr. Curren's letter without a single
- comment. It seems out of character for you to allow such complaints to go
- without response. Are you aware that your silence gave the impression that you
- concurred with his statements?
- Sincerely,
- Kim Schumann
- Chief Software Engineer
- SoftC, Ltd.
- 16820 Third Street N.E.
- Ham Lake, MN 55304-4703
- Thank you for clarifying the royalty issue. As for not answering the letter, I
- sincerely hope everyone knows that letters from readers represent only their
- authors' opinions, not mine nor the magazine's. We publish all kinds of
- letters we don't agree with; that's part of being a forum. Many factors
- influence whether a specific letter gets answered: whether we have room for
- the answer, whether I know anything about the subject, whether I think it
- deserves legitimization, whether the muse is sitting with me.
- We certainly didn't mean to endorse Curren's opinions by not responding. I
- apologize if the letter gave that impression. Thanks for the support. -- rlw
- Attn: Rex Jaeschke
- Dear Sir:
- I enjoyed your article in the Sept '90 issue concerning use of the qsort
- function. Under the subheading Multikey Sorts, however, you make the
- recommendation (as I understand it) to separately sort the primary key and
- then make smaller subsorts of each secondary key. Under any circumstances that
- I have been able to imagine, this should not be necessary as long as the sort
- function passed makes its determinations giving the primary key full priority
- over the secondary, that is the secondary field is only used if a complete
- match occurs in the primary. This has always worked in any place I have used
- it.
- Sincerely,
- Jay Holovacs
- 95 King George Rd.
- Warren, NJ 07059-6921
- I agree. Thanks. -- Rex
- Dear Mr. Plauger,
- Here are a couple more solutions to Dale Wharton's problem. They should work
- regardless of the host character set. The first (printc) puts out a format
- suitable for re-encoding with sprintf (when using the same character set). The
- second (printasciic) decodes ASCII. Both are shown in Listing 1.
- Your magazine has been one of the most useful I have ever subscribed to. I
- keep digging into my stack of past issues looking for things I remember are
- there somewhere. Do you have any plans for indexing past issues? I've also
- enjoyed your work since Software Tools. My copy of Standard C is already
- looking pretty beat up. It's good to see you step in as editor of the jounal.
- Sincerely,
- Eric Blossom
- 2737 Russell Street
- Berkeley, CA
- Those are both useful functions. I can't count how many times I have cobbled
- versions with only part of the needed functionality. Thanks for passing them
- on. And thanks for the kind words. -- pjp
- And yes, we are planning a comprehensive index... soon? -- rlw
- Editor:
- I often use the following code when it makes more sense than a do-while loop:
- #define until (expression) \
- while (! (expression))
-
- do {.....
- }
- until (something happens);
- A typical use would be to read the keyboard and stay in the loop "until the
- key pushed == ESC." It's just easier to think of than to stay in the loop
- "while the key ! = ESC". Or for infinite loops:
- #define HELL_FREEZES_OVER 0
-
- do {.....
- }
- until (HELL_FREEZES_OVER);
- Sincerely,
- Donald Gessling
- Dynatech Nevada
- 2000 Arrowhead Dr.
- Carson City, NV 89706
- If you feel that helps readability, fine. Just be careful when you ship code
- to other folks - they may not agree with your notions of a good language. I
- have seen C altered, via macros, to read like Pascal PL/I, and Algol 68. That
- can certainly please recent converts, but it can interfere with maintenance if
- you go overboard. -- pjp
- Dear Mr. Ward,
- I have just received your final notice of my subscription expiring, and am
- taking the time to inform you as to why I'm letting it go.
-
- First, let me point out the admirable aspects of your magazine; it deals
- wholly in the C family of languages, your Q&A section is unique in its ability
- to answer questions without belittling the asker, there's a good mix of narrow
- band and wide band articles with respect to their intended audience, and the
- regular authors generally cover important topics for the professional
- programmer. Taken as a whole, these qualities represent a formidable arsenal
- for the subscriber/regular reader.
- Unfortunately, there is one severe drawback in your implementation of The C
- Users Journal: source code. It simply is that important. Let me illustrate
- through example. Suppose you buy a paper which describes a fantastic squash
- casserole. You say to yourself, "Gee, I really like squash, how do I make
- this?" Upon close examination of the paper, you find that it will cost you two
- to four times as much for the recipe as it did to hear the description! You
- may be miffed the first time, but after twelve issues of the paper, each time
- seeing something that would be nice to make (without violating any copyrights,
- of course), and each time being reminded that you'll have to pay again for
- something that should have been in there to begin with, you get to the point
- of disgust. This is why I am no longer interested in maintaining my
- subscription with CUJ.
- Since I have defined a problem, it is only proper that I try to define a
- solution. There are two options that I can think of, though I'm sure I haven't
- exhausted them all. First, what of an online listings service (BBS)? Doctor
- Dobb's Journal uses one with great success. They archive the listings and name
- them with the three letter month mnemonic and the year of the issue (ie,
- aug90.arc). the users could then retrieve any listing they desired at their
- convenience. Second, printing the listings in the magazine itself would solve
- this dilemma, but would increase the number of pages in the magazine. Paper is
- everywhere, subscribers are not. Right now, the cover price is $4.50 and the
- subscription price is $2.25. This comes to approximately 3.1 and 1.6 cents a
- page, respectively. Is your overhead that high?
- Each possible solution described above assumes that you do not wish to make a
- profit from your followers twice for one thing: solutions. Since I tire of
- being teased, I shall wait patiently until you deem it fitting to freely allow
- access to the source listings (those that are allowed access by the
- copyrighters' consent). At this time, I shall enthusiastically resubscribe. I
- have a lifetime to wait, can CUJ afford that luxury?
- Regretfully,
- Steve Vickers
- Apopka, FL 32712
- I too am regretful. You write a very rational letter, arguing a position with
- which I totally agree. But I don't understand -- why do you perceive us as
- withholding source code?
- You say one solution is to print the listings in the magazine. That's exactly
- what we do! Apparently we've inadequately communicated something, please help
- me figure out what.
- In all but a few rare exceptions (where the listings would run hundreds of
- pages), we print full source code with each article. We also offer this same
- source code on disk for a $5.00 service fee. Perhaps we haven't explained the
- code disk well enough? Occassionally we put extra goodies on that disk (like
- maybe a shareware library that was mentioned in a story), but aside from such
- bonuses, the code disk is just a machine readable copy of the source that
- appeared in the magazine. We're certainly not trying to force you to buy the
- code disk by holding back essential source!
- Are you referring to the code on CUG volumes? We report on what's available in
- the CUG library, just as we report on developments on Usenet and other PDS
- sources -- surely you don't expect us to print all that source just because we
- mentioned it? We're talking about multi megabytes of source here!
- As for putting the source on a bulletin board, right now I don't think we have
- the staff to properly administer such a service (keeping the overhead low and
- all that). We hope to remedy that in the next few months, either by adding
- staff or making an arrangement with a reputable volunteer site. But
- nevertheless, since printing the source is an acceptable alternative for you,
- I don't understand why you think we've failed. -- rlw
- Dear Dr. Plauger:
- I have another interpretation of the question from Mr. Wharton (November 1990,
- We Have Mail). If he is using an IBM-PC style computer, control characters
- have been assigned various symbols such as card suits, arrows, and smiley
- faces. The catch in displaying these characters "the way printed charts
- picture them" is not with C, but with DOS and BIOS. When characters such as
- bell and line feed are sent from the C environment to DOS (via printf(),
- putchar(), etc.) they are executed with their traditional meanings, which is
- what most of us want most of the time.
- In order to display the special PC symbols with the same codes, it is
- necessary to bypass DOS and call the BIOS directly. (See Listing 2.) Books on
- PC graphics or PC hardware that explain other interesting BIOS functions
- should be available at any computer bookstore.
- Sincerely,
- Bob Raemer
- 8960 Neill Lake Rd.
- Eden Prarie, MN 55347
- Thanks to you, too -- pjp
- Dear Mr. Ward:
- In the October issue, Andrew Hollands mentioned Modula-2, and you expressed
- interest in hearing from readers who use it extensively. I have been
- programming professionally for many years and have used many languages.
- Excluding the research and experimental languages which are not generally used
- or available, Modula-2 is my clear favorite.
- When Wirth first published the description of Modula-2 around 1982, I was a
- bit disappointed in it. Since then, a few of the original problems have been
- fixed, and my point of view has changed in light of the alternatives. There
- are still a couple of things about Modula-2 that I wish were different, but
- overall, I find it a great productivity enhancer.
- The best summary I can give is that Modula-2 has the necessary trapdoors so
- that you can do low-level (and probably machine-dependent) stuff when you
- really need to, but it provides a lot of safety most of the time, when you
- don't. You won't fall into a trapdoor inadvertently.
- By contrast, C forces you to use too low-level constructs in several extremely
- common situations where they aren't needed, because there is nothing else. My
- favorite examples are explicit use of pointers when arrays and by-reference
- parameters are really needed, and the fact that every pointer is really a
- pointer to one element of an infinite array, whereas a pointer to a single
- object or into a bounded array is usually what is really needed.
- Modula-2 also has a type-safe separate compilation facility, and a method of
- packaging data structure with the procedures that operate on it. While these
- features have been regarded as standard techniques for many years by
- programming language researchers, Modula-2 and Ada are the only languages with
- any degree of popularity which fully support them. (Some of the
- object-oriented languages finally emerging do provide an improved variation of
- the latter feature.)
- I have also used Ada professionally for about five years now, and I continue
- to be more and more disillusioned with it. It has a lot of useful features,
- including a few which Modula-2 lacks. However, it also has a mind-numbing
- array of complex and bizarre rules that depend on every imaginable factor
- short of the phase of the moon. A colleague recently remarked that even after
- five years programming in Ada, he still frequently encounters a simple
- declaration or statement whose real meaning he can't be confident he
- understands, without spending hours poring over the reference manual.
- A lot of the desirable features of Modula-2 which distinguish it from original
- (and ISO) Pascal have also made their way into the various implementations of
- Pascal too. Pascal compiler implementors have apparently recognized these as
- valuable, and put them in as compiler-specific extensions to the language.
- However, since Modula-2 got these in the (original or revised) language
- description, they are more portable than in the dialects of Pascal. Also, no
- Pascal that I know of has separate interface and implementation modules. The
- popular UCSD Pascal units support type safe separate compilation, but they
- force massive recompilation, if you have to touch one.
- A standardization effort for Modula-2 is well underway. To some extent, it too
- suffers from some of the usual problems stemming from trying to solve tough
- technical dilemmas while immersed in political battles. However, the process
- has begun much earlier in the history of the language than usual. There is not
- nearly so great a proliferation of dialects as there were of Pascal and C,
- when standardization of those languages began. Hopefully, this will lead to a
- standard which is not so far out of sync with established practice, and which
- will be better accepted.
- "Standard" Modula-2 does, of course, lack object-oriented features. Despite
- all the excessive hype, object-orientation has tremendous value for
- programming. There is an object-oriented dialect of Modula-2 from JPI. This is
- even better than plain Modula-2, unless you care about portability, as I do.
- In that case, you can hardly afford to use the object-oriented features, since
- they are unique to one machine, one operating system, and one compiler.
- (Perhaps some day, I will write a letter about Modula-3, which, though little
- known, is considerably more powerful than Modula-2, is almost as simple, and
- has object-oriented features in its original report.)
- Mr. Hollands refers to Modula-2 as a "crutch," and you state that debugging in
- C (but presumably not in Modula-2) is too difficult for students. I have heard
- from countless people who believe that, for an experienced programmer who has
- the ability to cope with a more difficult language like C or Ada, an
- easier-to-use language like Modula-2 has no real advantage. I disagree. I can
- cope with the difficult languages as well as anybody, but I also care about my
- own productivity. I prefer to devote as much of my programming skill as
- possible to solving real problems which could not be solved for me
- mechanically by a compiler for a programming language which uses well-known
- language technology.
- Modula-2 has been slowly growing in popularity for a long time. We have all
- heard the argument over and over, that the technical quality of a programming
- language has little to do with its actual success. What will actually happen
- to Modula-2 remains to be seen, but I think it looks more likely than ever
- that it will be successful. It already enjoys far greater popularity in Europe
- than in the United States. Even here, it is already sufficiently successful
- that there is little doubt that a compiler will be available for most any
- machine/operating system you decide later to port your code to. In the
- meantime, I am having more fun than I've had with any programming language in
- 25 years and getting more code working in less of my time.
- Rodney M. Bates
- 1513 Blue Spruce
- Derby, KS 67037
- Thanks for a very thoughtfully-written letter. -- rlw
- I agree that Modula-2 is a definite improvement over Pascal. I also agree that
- a programming language should do as much of the dirty work as possible. It
- should minimize the number of error-prone operations you have to perform.
- My observation, however, is that Modula-2 is not spreading like wildfire. It
- is true that the language is more popular in Europe than in the U.S. So, too,
- were Algol 60, Algol 68, and Pascal -- none of which have survived as serious
- commercial programming languages. If you gamble on widely available compilers
- and support tools soon, you're taking a serious risk.
- What makes a language a rip-roaring success is not always easy to quantify or
- articulate. Many find C, and FORTRAN before it, inelegant or outright
- dangerous. Others simply notice that you can get a lot of work done by
- programming in C. I'm just grateful that the Elegance Police don't have much
- power. I prefer the freedom of an open marketplace.
- If the marketplace makes Modula-2 a winner in the next few years, I'll have no
- objections. Someday, we may even see a Modula-2 Users Journal. For now, I just
- view languages like this as interesting experiments.
- But then, I'm notoriously pragmatic. -- pjp
- Dear Robert Ward,
- I am a developer who is working with C for the Windows platform. I am looking
- to purchase a library of statistical routines, the most important of which is
- Multiple Regression (linear fitting).
- My first choice for the library is one that is written for the Windows
- environment. However, if such a thing does not exist I will settle for a
- library for which source code is available. The source code will have to be
- written in C.
- Any help that you can give me in this area is immensely appreciated. Thank
- your for your kind attention.
- Avi Farah
- Price Waterhouse
- 65 Madison Avenue
- Morristown, NJ 07960-1940
- Both CUG 266 microPLOX and CUG334 GNUPLOT are not statistics packages, but
- would provide an excellent tool to help your statistical analysis. Both
- packages take commands and data input and display or draw graphs/charts on
- monitors or printers. microPLOX would be appropriate for drawing a bar chart,
- while GNUPLOT is capable of drawing graphs or plotting data points based on a
- given mathematical function (built-in or user-defined), such as sin(x),
- cos(x), etc. GNUPLOT supports an extensive set of graphics drivers including
- PostScript.
- With these tools, all you need may be some mathematical formula to implement
- statistical techniques. However, CUG library still lacks an integrated
- statistical package, for which we count on submissions from readers. -- Kenji
- Hino
- Dear Mr. Plauger,
- I received your special introductory offer in the mail today to subscribe to
- The C Users Journal. I don't usually write to people who send me offers to
- subscribe to a magazine; this is the first time, but I have a suggestion for
- you which may help increase the number of subscribers that you have.
- I subscribed to your magazine a while back, as I was attempting to learn C. I
- had purchased Borland's Turbo C, along with a couple of books on C. I was
- struggling to learn the language; it was unlike Basic or dBase, which I had
- taught myself by reading the manuals and trying different things out. The C
- language is very powerful and unique!
- I found your magazine to be more in depth than what I was ready for at that
- time. The reason I'm writing to you now is I think there is a market for a
- publication that addresses itself to the beginning and intermediate market.
- There are a lot of people out there like me who bought and continue to buy
- Turbo C or Quick C because they want to learn the language, but the
- complexities and new concepts like pointers are just overwhelming at first.
- Why don't you tap into that market?
- You could create a separate magazine that holds the hand of the beginner and
- teaches him basic things, and which also has sections of interest to the
- intermediate C programmer. As the person became more advanced, he could move
- up to The C Users Journal. Alternately, you could just expand The C Users
- Journal to cover topics of interest to all, from beginner to expert, but that
- would be more intimidating to the new user, as well as a partial waste for the
- advanced user. By having two separate publications, you're not sending out a
- lot of pages that a subscriber has no intention of reading.
- You have a wonderful library, and you have the resources to do it. You
- definitely have the technical expertise to do it, and there's no magazine on
- the market at this time that I'm aware of that caters to beginning C
- programmers. The market's all yours!
- If you should decide to expand and go for the beginning market, let me know
- and I'll be more than happy to subscribe, and I'm sure a lot of other people
- will too. You could even include a flyer in packages of Turbo C and Quick C to
- let new buyers know of your publication(s).
- Thank you for taking the time to consider my proposal, and I look forward to
- hearing from you soon. I wish you the best of luck in your publishing
- endeavors!
-
- Sincerely,
- Vincent Harris
- P.O. Box 1324
- Sebastopol, CA 95473
- A magazine can't be all things to all people. We do try to publish a range of
- editorial material, some for beginners and some for advanced C programmers.
- Keeping both a broad range and a tight editorial focus is a continual
- balancing act. We can only keep trying to get it right.
- Starting a new magazine is a publisher's decision. That's Robert Ward's
- domain. I just edit here. -- pjp
- Dear CUJ:
- In your November, 1990 article "A Flexible Dynamic Array Allocator" by Dick
- Hogaboom, I located a small typo that keeps the routine from correctly
- initializing arrays. On page 118,
- p_data = base + data_off * ptr_size
- should be
- p_data = base + off(num_dim-1) *
- ptr_size
- Regards,
- Jon Michaels
- 61 Cutler Road
- Greenwich, CT 06831
- Thanks -- pjp
-
- Listing 1
- /* show.c - Demonstrates some character
- printing functions */
- #include <stdlib.h>
- #include <stdio.h>
- #include <string.h>
- #include <ctype.h>
-
- #define showx(a) printf(isprint(a) ? \
- "%c": "\\x%\.2x", a)
-
- void
- showc( int c )
- {
- static char esc[ ] = "\a\b\t\n\r\f\v";
- static char *ptr[ ] = {
- "\\a", "\\b", "\\t", "\\n",
- " \\r ", " \\ f ", "\\v"
- };
- char *s = strchr(esc, c);
-
- if (s && c)
- printf ( ptr[s-esc] );
- else
- showx( c );
- }
-
- void
- showasciic(int c)
- {
- static char *a[ ] = {
- "<NUL>", "<SOH>", "<STX>", "<ETX>",
- "<EOT>", "<ENQ>", "<ACK>", "<BEL>",
- "<BS>", "<HT>", "<LF>", "<VT>",
- "<FF>", "<CR>" "<SO>", "<SI>",
- "<DLE>", "<DC1>", "<DC2>", "<DC3>",
- "<DC4>", "<NAK>", "<STN>", "<ETB>",
- "<CAN>", "<EM>", "<SUB>", "<ESC>",
- "<FS>", "<GS>", "<RS>", "<US>"
- };
-
-
- static char b[ ] =
- " !\"#$%&'( )*+,-./0123456789:;<=>?" \
- "@ABCDEFGHIJKLMNOPQRSTUVWXYZ [\\]^" \
- "'abcdefghijklmnopqrstuvwxyz{}~";
-
- if (c & ~0x7f) ( /* not ASCII */
- printf ("<");
- showx(c);
- printf ("?>");
- }
- else { /* ASCII */
- if {c == 0x7f)/* DEL */
- printf ("<DEL>");
- else if (c & ~0xlf) /* graphic */
- putchar(b[c-32]);
- else /* control */
- printf (a[a]);
- }
- }
-
- void
- main(void)
- {
- int c;
-
- for (c = -1; c < 129; c++)
- showc (c);
- putchar ('\n');
-
- for (c=-1; c<129; c++)
- showascii(c);
- putchar { ' \n ' );
- }
-
-
- Listing 2
- /* works with Microsoft C V6.0, PC with EGA */
-
- #include <stdio.h>
- #include <dos.h>
-
- void cchar(int chr, int color, int background, int blink, int page);
-
- #define BLACK 0
- #define BLUE 1
- #define GREEN 2
- /* etc. */
-
- void main()
- {
- printf("%c", 65); /* prints 'A' */
- printf("%c", 1); /* prints smiley face */
-
- printf("%c", 7); /* rings bell */
- putchar(7); /* rings bell */
-
- /* above functions call DOS (probably via INT 21H), which
- ** executes some control codes with their traditional meaning.
- */
-
-
- /* now bypass DOS and use ROM-BIOS * /
- cchar(7, GREEN, BLACK, 1, 0); /* blinking green diamond */
- }
-
- /* color character at present cursor location */
- void cchar( int chr, int color, int background, int blink, int page)
- {
- union REGS regs;
-
- regs.h.ah = 9; /* write character function */
- regs.h.al = chr; /* character code */
- /* attribute */
- regs.h.bl = (blink ? 0x80 : 0) ((background & 7) << 4) (color & 0x0f);
- regs.h.bh = page; /* display page */
- regs.x.cx = l; /* repetition count */
- int86(0xl0, ®s, ®s); /* call BIOS video function */
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Removing Recursion From Algorithms
-
-
- Gary Syck
-
-
- Gary Syck is a programmer for Crystal Point in Botholl, Washington. He has 10
- years programming experience, including seven years in C. You can contact him
- at 12032 100th Ave. NE, #D203, Kirkland, WA 98034.
-
-
- Recursion is a powerful tool for the algorithm designer. The idea is to break
- a large task down into a number of identical sub-tasks and have the recursive
- routine call itself for each sub-task. This tool simplifies tasks such as
- reading all of the files on an MS-DOS system. Instead of trying to come up
- with a routine that visits all of the sub-directories, you just develop a
- routine to read a single directory that calls itself whenever a directory item
- is a sub-directory. What once was a large task is now a manageable one. In
- this article, I will demonstrate how to create recursive algorithms and then
- convert them to non-recursive routines to avoid some of the problems with
- implementing recursive routines on microcomputers.
- When designing a recursive algorithm, you must first ask yourself if the task
- can be broken down into a number of identical sub-tasks. Next, determine under
- what conditions the recursive routine exits. Knowing these conditions is
- important, since the routine must not only exit from any given instance but
- must also exit from all previous instances. Later in this article you will see
- two examples of how to develop recursive algorithms.
- Recursion loses some of its appeal in actually implementing the routine. Some
- languages do not allow recursion at all. Others such as C give you just enough
- rope to get yourself into trouble. The source of this trouble is often related
- to stack usage. Each time the recursive routine calls itself, it must save any
- registers it uses and the return address on the stack. Then the new instance
- of the routine makes room for a fresh copy of all of its local variables on
- the stack.
- Consider the directory reading example previously mentioned. If the program is
- compiled using the large model, a string pointer argument (the path to read)
- takes eight bytes. This routine uses stack space to hold directory entries (32
- bytes) and miscellaneous variables to keep track of the search (say eight
- bytes each). The total stack usage is 48 bytes for each call. If you call this
- routine from deep within a program that has little stack space to begin with
- (for example a TSR), you will get some interesting errors as the stacks grows
- up into whatever precedes it.
- To show how to design recursive algorithms, this article presents two
- recursive routines. Once the routines are designed and coded in a recursive
- fashion, you can look at how to make them non-recursive to keep stack usage
- under control. After that you can compare the recursive and non-recursive
- versions to determine the possible benefits of this approach.
- The first routine is for searching binary trees. In this case the tree is
- arranged such that the left child of any node is less than the root and the
- right child is greater than the root. The goal of the routine is to locate a
- specific value in the tree. This problem can easily be broken down into
- identical sub-tasks, each of which compares the value to find with a node. If
- the value to find is less than the node, get the left child. Otherwise, get
- the right child. The routine calls itself with each new node, until it finds a
- node without the required child, or it finds one that matches the value.
- Listing 1 shows this algorithm coded in C. There are two cases to check to see
- if the routine should return. The first is if the value being tested matches
- the value for the node. In this case the search is successful and the routine
- returns the node number. The other case is if the required child node does not
- exist. This indicates that the value is not in the tree so the routine returns
- a --1. Each instance of the routine returns the result of the child instances
- until the value is finally returned to the original calling routine.
- The next routine shows how to use recursion to sort a list of items. This is
- the well-known quicksort algorithm. It works by dividing the list into two
- parts. The parts are arranged such that all of the items in the first part are
- less than all of the items in the second part. Then, each of the parts are
- subjected to the same procedure. Eventually, the part is a single item so it
- must be sorted. The use of recursion in this algorithm is obvious: the routine
- need only make the two parts, then call itself twice, once for each part.
- The tricky part of this algorithm is to make the two parts in the first place.
- Many techniques have been proposed, but the actual technique used is not
- important for this article. Listing 2 shows an implementation of the quicksort
- algorithm that uses a technique from Sedgewick.
- Both of these routines handle a difficult problem efficiently. The tree search
- routine can find an item in a large tree with very few compares. The quicksort
- algorithm out-performs many other sort techniques for sorting randomly ordered
- data sets. One bottleneck common to both of these routines is the overhead
- required to do the recursion.
- The tree search routine must do a function call for each node that it visits.
- In addition, once the desired node is found, it must do a number of returns to
- get that information back to the calling routine. Stack usage for this routine
- depends on how "balanced" the tree is. If the tree is perfectly balanced (all
- nodes without children are in the last row), the number of visits to find any
- given node is less than log2 of the size of the tree. For a 65,536 node tree,
- fewer than 16 visits are required. The impact on the stack in this case is
- minimal. On the other hand, in a worst-case unbalanced tree the routine may
- need to visit every node in the tree to find a node. In this case you may need
- more than the available stack space to run this routine.
- Since the quicksort algorithm divides the list into two parts, it has the same
- stack usage properties as the binary tree search. If the routine to divide the
- list into two parts divides the parts evenly, there will not be many recursive
- calls. To make the partitioning routine efficient, it rarely divides the list
- evenly. As with the binary search, this routine may make as many recursive
- calls as there are items in the list.
- Both of these routines can be converted to non-recursive versions that remove
- the overhead of the recursive calls. In both examples, removing recursion
- improves the execution speed of the routine. In the tree search routine it
- also removes the requirement for extra memory for each instance of the
- routine.
- A recursive call accomplishes two tasks. First, it changes the arguments for
- the next instance. Second, it returns to the beginning of the routine. You can
- change the arguments by modifying the formal parameters. In the tree search
- routine this means setting NodeNumb to the number of the next node to look at.
- You can use a goto command to get back to the beginning of the routine.
- Next, the exit condition needs to be changed. The return statements in the
- recursive version of a routine must be changed to a goto to the instruction
- past the recursive call. When there is more than one recursive call, you need
- a flag to show which call to jump past. When this is done to the tree search
- routine, you can easily see the advantages of the non-recursive routine. Since
- there is only one instance, there is no need to pass the final result up
- through previous instances. The routine can immediately return to the caller
- with the result.
- Once the recursive routine has been converted, you may want to examine it to
- see if it can be cleaned up a bit. Listing 3 shows the tree search routine
- after it has been cleaned up. The first step was to notice that the two calls
- could be combined into a single goto with different values in NodeNumb. After
- that change, the routine looked like a while loop that exits with a null node
- or a matching node. Using a while statement gets rid of the gotos and results
- in an elegant non-recursive routine.
- Complications come about when converting the quicksort routine. After the
- recursive call to divide the first half, you have to make another call to
- divide the second half. Of course, the first half, in turn, has a first and
- second half. The question is, "Where do you store the variables that define
- the halves?" The answer is to make a stack, and push the beginning and end of
- each half left to sort onto the stack. The routine is finished when the stack
- is exhausted.
- If you have to make a stack to eliminate recursion, what is the advantage? The
- first advantage is that this stack can hold more halves than the recursive
- stack because there are no stack frames on it. The other advantage is that you
- can monitor the amount of data in the stack and use a different strategy when
- it gets full.
- Listing 4 shows the modified quicksort routine. It pushes the beginning and
- end of each half to sort on the stack, then pops one half off the stack before
- looping back to the beginning. In this case, the loop with gotos resembles a
- do..while loop that exits when there are no items left in the stack. There is
- also logic to determine when the stack is full. If the stack is full, the
- quicksort routine calls a shell sort routine to sort the section.
- In both the tree search routine and the quicksort, removing recursion makes a
- more efficient routine by eliminating time-consuming function calls. In
- addition, it eliminates the danger of stack overflow. If these routines are
- going to be part of a library, you should use the non-recursive versions.
- Some algorithms would be difficult or impossible to discover if recursion were
- not available. The algorithm designer must consider recursion as a possible
- solution for any problem that could be divided into smaller parts. Once a
- recursive algorithm has been found, you can use the techniques demonstrated in
- this article to convert it to a more efficient and less dangerous,
- non-recursive routine.
-
- Listing 1
- int
- TreeSearch( int NodeNumb, int Value )
- {
- if( NodeNumb = -1 Value == Tree[NodeNumb].Data )
- return NodeNumb;
- if( Value < Tree[NodeNumb].Data )
- return TreeSearch( Tree[NodeNumb].Left );
- else
- return TreeSearch( Tree[NodeNumb].Right );
- }
-
-
- Listing 2 Recursive Quicksort Routine
- void
- QuickSort( int *List, int Begin, int End )
- {
- int Value, Tmp, i, j;
-
- if( End > Begin )
- {
- /* Divide the list in two */
- Value = List[End];
- i = Begin - 1;
- j = End;
- do {
-
- while( List[++i] < Value );
- while( List[--j] > Value );
- Tmp = List[i];
- List[i] = List[j];
- List[j] = Tmp;
- } while( j > i );
- List[j] = List [i];
- List[i] = List[End];
- List[End] = Tmp;
- /* Sort the first part */
- QuickSort( List, Begin, i - 1 );
- /* Sort the last part */
- QuickSort( List, i + 1, End );
- }
- }
-
-
- Listing 3 Non-Recursive Tree Search Routine
- int
- TreeSearch( int NodeNumb, int Value )
- {
- while( NodeNumb != -1 && Value
- ! = Tree[NodeNumb].Data )
- {
- if( Value < Tree[NodeNumb].Data )
- NodeNumb = Tree[NodeNumb].Left;
- else
- NodeNumb = Tree[NodeNumb].Right;
- }
- }
-
-
- Listing 4 Non-Recursive Quicksort
- /* Quick sort routine (not recursive) */
-
- /* Prototypes */
- void QuickSort( int *List, int Begin, int End );
- void ShellSort( int *List, int Begin, int End );
-
- #define MAXSTACK 200
- int Stack[MAXSTACK];
- int StackPoint;
-
- void
- QuickSort( int *List, int Begin, int End )
- {
- int Value, Tmp, i, j;
-
- StackPoint = 2;
- do
- {
- /* Divide the list in two */
- Value = List[End];
- i = Begin - 1;
- j = End;
- do {
- while( List[++i] < Value );
- while( List[--j] > Value );
- Tmp = List[i];
-
- List[i] = List[j];
- List[j] = Tmp;
- } while( j > i );
- List[j] = List[i];
- List[i] = List[End];
- List[End] = Tmp;
- /* If more than 2 items push the first part */
- if( i - Begin > 2 )
- {
- if( StackPoint >= MAXSTACK )
- ShellSort( List, Begin, i-1 );
- else
- {
- Stack[StackPoint++] = i-1;
- Stack[StackPoint++] = Begin;
- }
- }
- if( End - i > 2)
- {
- if( StackPoint >= MAXSTACK )
- ShellSort( List, i+1, End );
- else
- {
- Stack[StackPoint++] = End;
- Stack[StackPoint++] = i+1;
- }
- }
- Begin = Stack[--StackPoint];
- End = Stack[--StackPoint];
- } while( StackPoint );
- }
-
- /* Shell sort for when the stack is full */
- void
- ShellSort( int *List, int Begin, int End )
- {
- int i, j, Mid, Value;
-
- for( Mid = 1; Mid <= End - Begin + 1; )
- Mid = 3 * Mid + 1;
- do {
- Mid /= 3;
- for( i=Mid+1; i <= End - Begin + 1; i++ )
- {
- Value = List[Begin+i];
- j = i;
- while(j > Mid
- && List[Begin+j-Mid] > Value )
- {
- List[Begin+j] = List[Begin+j-Mid];
- j = j - Mid;
- }
- List[Begin+j] = Value;
- }
- } while( Mid != 1 );
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Skip Lists
-
-
- Frederick 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.
-
-
- Maintaining some sort of ordered list must surely be one of the most common
- problems in programming. An ordered list can be a symbol table, a data
- dictionary or the distribution of periodic readings from some instrument.
- Maintaining an ordered list is also one of the most commonly studied,
- analyzed, refined, and seemingly well understood problems.
- That's why running across a completely novel approach is a bit unnerving, like
- finding a clam with legs. William Pugh's article, "Skip Lists: A Probabilistic
- Alternative To Balanced Trees," in CACM (June 1990), presented a novel data
- structure with characteristics intermediate between those of simple linear
- linked lists and perfectly balanced binary trees. I know a clam with legs when
- I see one.
-
-
- Skip Lists
-
-
- Skip lists are linked lists with extra pointers that leapfrog, or skip over,
- intermediate nodes during search procedures. Because the extra pointers are
- assigned by consulting a random number generator, search times are independent
- of the ordering of the data input during list construction. Figure 1 diagrams
- a skip list that might be built by the test program with a command line of
- sltest 7 4 2 n x
- If you start searching along the pointer chain marked level one, you are
- obviously searching along a simple one-way linked list, with a NIL pointer
- marking the end of the list. Each key along the chain is examined in turn. If
- the key compares less than the searchkey, you look at the next node. If it
- compares equal, you have found the desired node. If it compares greater than,
- the key you are searching for is not in the list. If you reach the NIL
- pointer, the key is not in the list.
- The strategy for searching the skip list is similar. Find the highest level
- pointer in the list header and follow the pointer chain. If you reach a NIL
- pointer, decrease the level one step. If the level drops to zero, the key is
- not in the list; otherwise, continue along the new chain. If you reach a node
- with a greater key, you've gone too far and have to drop down one more level.
- If the level drops to zero, the key is not in the list; otherwise, continue
- along that chain.
- Pugh provides a great deal of analysis. I've taken a more informal approach.
- First, I build a skip list, and then I take a kind of snapshot of the way it
- looks and behaves.
-
-
- Contents Of skiplist.h
-
-
- Listing 1, skiplist.h, describes the node structure used to build the lists.
- In order to build the largest possible list, sizeof(NODE) has been kept
- artificially small, by omitting any data members. If you want to use skip
- lists in any real application, you will have to declare a member to hold or
- point to data. The structure is indifferent as to where you put the data
- member(s), as long as the dynamically sized array pointers is the last member
- of the structure.
- Listing 1 also defines several important constants, such as the name of the
- comparison function COMPARE. As written, the functions depend on a COMPARE
- function that conforms to the strcmp()/memcmp() model (returning negative,
- zero, or positive). Integers are used as the keys in the test program, both to
- avoid using memory for storing character strings and to keep comparison times
- to the minimum.
-
-
- Skip List Routines
-
-
- Listing 2, skiplist.c, contains the actual list maintenance routines. search()
- works exactly as above, returning a pointer to the desired node if successful,
- NIL if not. insert() and delete() are only slightly more complicated, needing
- to preserve the integrity of the pointer chains. In either case, we begin by
- recording all pointers that pass the indicated key. insert() returns
- immediately whenever the key is already in the list. delete() records that
- pointer also. The number of comparisons performed in this step is limited by
- the maximum node level currently in the list. In the case of delete(), one
- additional comparison is necessary to determine if the node actually exists.
- After fetching a new list node or deleting an existing one, the code
- reconstructs pointer chains in a loop governed by the level of the affected
- node.
- The list always begins with a pseudo-member, or header, with space for
- MAXLEVEL pointers and provision for recording the current level of the list.
- newlist() ensures that you have a header node large enough to hold the maximum
- number of pointers that may be allotted. The call to calloc() will initialize
- each potential pointer to NIL. The initial level is set to one, which is the
- minimum. newnode() gets memory for a dynamically sized node and initializes
- the key member of the structure.
- randomlevel() returns an integer from 1 through MAXLEVEL, based on the numbers
- chosen for DMAX (MAXLEVEL) and NMAX (PARTITION). The ratio NMAX/DMAX
- determines the distribution of node levels through the list. For example, if
- DMAX is 4 and NMAX is 2, the function slrandom() is called repeatedly with 4
- as its argument, incrementing the local variable newlevel so long as the
- returned value is 0 or 1. Note that the entire series of consecutive values
- less than NMAX is consumed. The length of the series determines the level.
- Given a random number generator with reasonable performance, the distribution
- will approach the programmer specified ideal where approximately one-half of
- all nodes will have only one pointer, one-fourth will have two pointers,
- one-eighth will have three, and only one-sixteenth of all nodes will have the
- maximum number.
-
-
- Random Number Routines
-
-
- The pseudo-random number routines are in Listing 3, slrandom.c. The functions
- slrand() and slsrand() and the constant SL_RAND_MAX mimic their ANSI standard
- library counterparts in <stdlib.h>. They are based on code published by the
- ANSI committee, using longs rather than unsigned longs to accommodate
- compilers that do not recognize unsigned long ints. I included them so you
- could reproduce results across compilers and machines. When you're tired of
- the test program, use the standard functions. The function slrandom() scales a
- pseudo-random number returned from the standard function to fit within a
- specified range. Your compiler libraries may already have something similar.
- slrandom() provides a good distribution of values within a range -- other
- schemes may not.
-
-
- Test Routines
-
-
- Listing 4, sltest.c, contains the code for a simple test program to build and
- examine some skip lists. Five parameters are entered on the command line as
- unsigned integers -- the size of the list to build, the value of MAXLEVEL, the
- value of PARTITION, a seed for the pseudo-random number generator, and a flag
- to choose between random insertion of keys or an ordered insertion. None of
- these routines is very interesting on its own. A skip list of the indicated
- size, or of the largest size allowed by memory, is built as requested. Certain
- calculations are handled most easily by lookup tables, so the maximum size you
- can request is limited to 32,767, which will probably exceed the size of any
- list you can actually build in memory. Once the list is built, each element
- known to be in the list is searched for in its turn. I use the number of
- comparisons performed during a search as the measure of the length of the
- search path. This information is collected, organized, and output to stdout as
- shown in Figure 4.
-
-
- Skip Lists
-
-
- When you look at Figure 4, start with the line "pointers per level." No node
- has more than seven pointers, far fewer than the maximum allowed. About
- three-fourths of all nodes have only one pointer. The number of nodes with two
- pointers is approximately one-fourth the number at level one. Likewise with
- levels three and four. A little calculation shows that 4,050 pointers (not
- counting 16 pointers in the header) are sufficient to maintain a skip list of
- 3,000 entries -- 1.35 pointers per node. If trees always require two pointers
- per node, the savings is 1,950 pointers -- most probably 3,900 or 7,800 bytes.
- In general, the average number of pointers per node will be 1/((MAXLEVEL --
- PARTITION)/MAX-LEVEL), a constant figure independent of the length of the
- list. The expected figure here would be 1.33 pointers per node. Unless the
- list is very short, you can make a reasonably close calculation of memory
- requirements even though you can't predict the structure of the list.
- The line "comparisons per search" is less interesting in isolation than when
- changing the command line parameters. Note, however, that you would have built
- a degenerate tree with the same input sequence.
- The simple graph is scaled so that the display will fit on a 24-line screen.
- Here, it shows a nice distribution of results about the mean. Very short lists
- can have distributors that are bi- or tri-modal, or even discontinuous. The
- random number routines simply don't cycle long enough to display any "random"
- behavior. The graph of comparisons over a reasonably long list sometimes
- starts looking less than bell-shaped. That tells you that one of the command
- line parameters needs to be adjusted to get better search properties.
- I consider a tree to be perfect when it requires the minimum number of
- comparisons to find each node in the tree. This situation occurs when each
- level of the tree, except, perhaps, the last level, is full. A degenerate tree
- is the same as a linked list. The figures for either are easily calculated and
- presented for comparison.
-
-
-
- Using sltest
-
-
- Twiddling the knobs can illustrate a number of things. First, varying only the
- random number seed and the flag parameter should demonstrate that a skip list
- of any reasonable length is indifferent to the order in which it was built.
- Once you're satisfied that this it true, you will find that processes go a
- little faster by always requesting in-order insertion.
- Second, the effect of varying the random number seed decreases as the size of
- the list increases. Building a series of short lists can seem chaotic, but
- only because the sequence of numbers generated never becomes long enough to
- become truly random.
- Third, varying the size of the list while keeping the other parameters
- constant might show some seemingly paradoxical behavior -- the mean number of
- comparisons goes down as the size of the list increases. It's only temporary.
- In general, the mean increases as the size of the list increases, but not
- always continuously. A strategic node is inserted, randomly of course, and
- many search paths are favorably affected. The list grows longer, and the mean
- begins to increase again. Pugh demonstrates that searching a skip list is
- 0(log N) -- as in searching a balanced tree. Intuitively, the cost of
- searching the list increases very slowly relative to the size of the list.
- This is the case if MAXLEVEL and PARTITION are in the proper proportions --
- both to each other and to the size of the list. A little doodling around
- should demonstrate that a ratio greater than one-half makes no sense at all.
- One-half works well but uses an average of two pointers per node. Pugh
- suggests the ratio one-fourth. Empirically, one-fourth seems an ideal balance
- between performance and memory usage, but why this should be a magic number is
- beyond my understanding.
- Tweaking the values for MAXLEVEL while maintaining the same ratio to PARTITION
- is less informative than it might appear. It is interesting to watch
- performance degrade as list size grows and/or MAXLEVEL decreases. Think of the
- distribution of pointers per node. If the distribution was exactly in our
- chosen ratio, we could determine the level where the expected number falls to
- one or below. Take a list of 32,768 elements. If the ratio were one-half, the
- level would be 15. If the ratio were one-fourth, the level would be nine. As
- MAXLEVEL starts to drop below expected levels, pointers begin to accumulate at
- the highest level, and search times increase. You might not have noticed how
- few nodes have been allotted at levels higher than strictly necessary,
- although I tried to hint at it previously. I would suggest that maintaining
- the ratio is more important than saving a few bytes allotted in the header, a
- few more that might or might not be needlessly allocated in one or two rogue
- nodes, and a negligible bit of stack space on insertion or deletion. A level
- of 16 will safely accommodate at least 65,536 nodes -- I would think very hard
- about what kind of memory space that would require -- and leaves more room for
- adjusting PARTITION, where you can preserve serious amounts of memory.
- Varying the value of PARTITION over a constant sized MAXLEVEL has another
- important effect that might not be readily apparent. That ratio, together with
- list size and random chance, has a bearing on the maximum comparisons per
- search. It seems obvious that the maximum should appear to increase as the
- size of the list increases. Changing the random number seed should cause the
- maximum to vary as different lists are built. Changing the ratio should affect
- the maximum as more or less leapfrogging gets done at higher levels. It might
- not be so obvious that as the ratio grows away from one-half, the maximum path
- length grows increasingly sensitive to changes in the random number seed, or
- chance. At the limit of one-sixteenth, the variations can be striking.
- Chance is the wildcard that I have been deliberately ignoring. There is always
- the chance that you might build a degenerate structure like one of those in
- the sidebar. You might as well get the worrying out of your system by using my
- random number routines and running
- sltest 16 3 1 35 1
- You may never see such a situation again. Why this should be so might be
- illustrated by going through the previous paragraph in reverse.
- As the ratio approaches one-half, the volatility of the maximum search path
- lessens. If MAXLEVEL was chosen intelligently, or allowed to default, the
- maximum search path in any list may vary but should remain within a
- comfortable multiple of the mean. This conforms to Pugh's probabilistic
- analysis. Instinctively, also, as the mean increases very slowly in comparison
- to increases in list size, any constant multiple of the mean is also
- increasing very slowly. At the same time, the observed maximum is becoming
- relatively less sensitive to the random number seed. It is behaving rather
- nicely as a multiple of the mean, and as a multiple of what we might guess to
- be the mean of the means, so to speak, of all the lists of that size that are
- being built. The result is a crude demonstration that the behavior is becoming
- more regular as the size of the list increases.
- This seems to me one of the most profound properties of skip lists. The
- probability of worst-case behavior decreases exactly as the consequences of
- such behavior become more severe. And, inversely, the probability of poor
- behavior is greatest exactly at that stage when dealing with it means the
- least. Build a list of 16 elements -- the size of the list is limited; the
- random numbers don't look so random; the algorithms construct a degenerate
- list; it might take 16 comparisons to find a member of the list. So what?
- Build a list of 3,000 elements and the chances can be less than one in one
- hundred million that it might take, not 3,000 comparisons, but as many as 80
- or 90 comparisons. It will not be likely to ever take 3,000.
-
-
- Conclusions
-
-
- The common choice for managing ordered lists of any real size has usually been
- binary trees. Binary trees are sensitive to the randomness of the keys from
- which they are built, so various schemes for building balanced trees have been
- developed to keep search times from getting out of hand. Search can be much
- faster, but insertion and deletion are much slower. How much slower? Baer and
- Schwab suggest that the number of searches per node should be at least three
- before you consider AVL trees over random trees. They also found that AVL
- trees perform better than other balancing algorithms if only insertions and
- searches are performed.
- Now take as input some arbitrary text, break it down into individual words,
- record each distinct word, keep a count of how often each word appears in the
- input, then output the list of words in alphabetical order, together with each
- word's count of appearances. Sound familiar? Think of what could go wrong. Do
- you really need, or want, to use AVL trees?
- Skip lists are simple and straightforward to implement. In-order traversal is
- an iterative procedure that operates in constant space, unlike recursive
- procedures applied to tree structures, which can corrupt the stack when the
- list is large or highly unbalanced. Insertion and deletion procedures are
- faster than those for AVL trees. Skip lists are space efficient and can yield
- good search performance using an average of 1.33 pointers per node. Although
- skip lists have terrible worst-case search performance, no predictable input
- sequence will create a degenerate structure, and no random sequence of
- insertions and deletions will create such a degenerate structure with any
- significant degree of probability. Performance is sensitive to the size of the
- list, the number of pointers allowed per node, and the fraction used to
- determine when a node is allotted an extra pointer. These, however, are
- subject to tuning by the programmer.
- I cannot recommend Pugh's approach too highly because skip lists have so many
- desirable properties.
-
-
- Bibliography
-
-
- Pugh, William. "Skip Lists: A Probabilistic Alternative to Balanced Trees."
- Communications Of The ACM. XXXIII, 6, June 1990, Pp. 668-676.
- Baer, J.-L. and Schwab, B. "A Comparison of Tree-Balancing Algorithms."
- Communications Of The ACM. XX, 5, May 1977, Pp. 322- 330.
- Knuth, Donald E. The Art Of Computer Programming: Vol. I, Fundamental
- Algorithms. Reading, MA, Addison-Wesley, 1968. Knuth goes deeply into lists
- and trees, though not, of course, skip lists.
- Skip Lists, Linked Lists And Unpleasant Thoughts
- Simple one-way linked lists might, in general, be built with a data structure
- something like this:
- struct node {
- char *key;
- int data;
- struct node *next;
- };
- It might not be readily apparent that the following is exactly equivalent:
- struct node {
- char *key;
- int data;
- struct node *next[1];
- };
- Turning things on their heads, you could describe the one-way linked list as a
- specialized skip list with MAXLEVEL of 1 and PARTITION of 0, where all nodes
- have pointers at the same level. Therefore searching in the list never
- leapfrogs over any node. In fact, any skip list where every node has the same
- level pointer is an expensive approximation to a linear list. Keep this in
- mind when setting MAXLEVEL. Too small a figure will deform the list and
- degrade performance significantly.
- You might be unfortunate enough to build a replica of Figure 2. It also
- emulates a linear list, even though the distribution of levels throughout the
- list is the same as in Figure 1. You might intuitively guess that degradation
- to a linear list is the limit on how bad things could get. Pugh, himself,
- described worst-case performance as the list where all nodes are level 1. But,
- consider the case of Figure 3. Clams with legs, indeed.
- Nobody would throw out quicksort because it might degrade to the performance
- of bubblesort. With a little thought, that possibility becomes manageable. The
- same applies here.
- Pugh's analysis is elegant and sophisticated. Using his methods, it is
- possible to show that even for relatively short skip lists of around 250 or so
- elements, the chances that a search path will exceed the expected length
- (about 15-16 comparisons in this case) by a factor of three can be about one
- in one million. The probability that the path length will actually resemble
- the list length is calculable but negligible, and grows even more remote as
- the length of the list grows longer.
- Figure 1
- Figure 2
- Figure 3
- Figure 4
- A>sltest 3000 16 4 673 0
-
- skiplist size = 3000
- pointers per level = 2231 567 145 40 14 1 2 0 0 0 0 0 0 0 0 0
- comparisons per search: mean = 18.131, minimum = 1, maximum = 39
-
- *
- ****
- *******
- *********
- ***********
- *************
- ***************
- *****************
- ********************
- ***********************
- ***************************************
- distribution from minimum through maximum: scale = 21:1
- for perfect trees: mean = 10.639, minimum = 1, maximum = 12
- for degenerate trees: mean = 1500.500, minimum = 1, maximum = 3000
-
- A>
-
- Listing 1 (skiplist.h)
- /* skiplist.h */
-
- #ifndef MAXLEVEL
- #define MAXLEVEL 16 /* must be a constant */
- #endif
- #ifndef PARTITION
- #define PARTITION 4 /* probably always a constant */
- #endif
-
- #ifdef SLTEST
- int intcmp();
- #define KEYTYPE int
- #define COMPARE intcmp
- #define DMAX maxlevel /* the denominator */
- #define NMAX partition /* the numerator */
- #else
- #define DMAX MAXLEVEL
- #define NMAX PARTITION
- #endif
-
- struct node {
- union {
- KEYTYPE key;
- int level;
- } korl;
- struct node *pointers[1];
- };
-
- #define NODE struct node
- #define NIL (NODE *)NULL
-
- #ifndef TRUE
- #define TRUE 1
- #endif
- #ifndef FALSE
- #define FALSE 0
- #endif
-
- /* declare skiplist.c routines */
- NODE *newlist();
- NODE *search();
-
- NODE *insert();
- int delete();
- NODE *newnode();
- int randomlevel ();
-
-
- Listing 2 (skiplist.c)
- /* skiplist.c */
-
- #define SLTEST 1
- #include <stdlib.h>
- #include <setjmp.h>
- #include "skiplist.h"
- #ifdef SLTEST
- #define NOMEM 1
- #endif
-
- /*
- * Skip list routines based on algorithms described
- * by William Pugh, SKIP LISTS: A PROBABILISTIC
- * ALTERNATIVE TO BALANCED TREES. CACM, XXXIII, 6,
- * June, 1990. Pp. 668 -- 676.
- */
-
- NODE *newlist()
- {
- NODE *temp;
-
- if(temp = (NODE *)calloc(sizeof(NODE) +
- (sizeof(NODE *) * (DMAX -- 1)), 1))
- {
- temp-->korl.level = 1;
- }
- return(temp);
- }
-
- NODE *search(searchlist, searchkey)
- NODE *searchlist;
- KEYTYPE searchkey;
- {
- NODE *list, *temp;
- int i, probe;
-
- list = searchlist;
- for(i = searchlist-->korl.level; ----i >= 0; )
- {
- while(temp = list-->pointers[i])
- {
- if((probe = COMPARE(temp-->korl.key, searchkey))
- < 0)
- {
- list = temp;
- }
- else if(probe == NIL) /* key found */
- return(temp);
- else
- break;
- }
- }
-
- return(NIL);
- }
-
- NODE *insert(searchlist, searchkey)
- NODE *searchlist;
- KEYTYPE searchkey;
- {
- NODE *lnode, *tnode;
- int i, newlevel, probe;
- NODE *tempptrs[MAXLEVEL];
-
- lnode = searchlist;
- for(i = searchlist-->korl.level; ----i >= 0; )
- {
- while(tnode = lnode-->pointers[i])
- {
- if((probe = COMPARE(tnode-->korl.key, searchkey))
- < 0)
- {
- lnode = tnode;
- }
- else if(probe == NIL) /* already present */
- return(tnode);
- else
- break; /* break from while loop */
- }
- tempptrs[i] = lnode;
- }
- /* key not yet present in list ---- insert it */
- newlevel = randomlevel();
- if(newlevel > searchlist-->korl.level)
- {
- for(i = searchlist-->korl.level; i < newlevel; ++i)
- {
- tempptrs[i] = searchlist;
- }
- searchlist-->korl.level = newlevel;
- }
- lnode = newnode(newlevel, searchkey);
- for(i = 0; i < newlevel; ++i)
- {
- lnode-->pointers[i] = tempptrs[i]-->pointers[i];
- tempptrs[i]-->pointers[i] = lnode;
- }
- return(lnode);
- }
-
- int delete(searchlist, searchkey)
- NODE *searchlist;
- KEYTYPE searchkey;
- {
- NODE *lnode, *tnode;
- int i;
- NODE *tempptrs[MAXLEVEL];
-
- lnode = searchlist;
- for(i = lnode-->korl.level; ----i >= 0; )
- {
- while((tnode = lnode-->pointers[i])
-
- && (COMPARE(tnode-->korl.key, searchkey) < 0))
- {
- lnode = tnode;
- }
- tempptrs[i] = lnode;
- }
- tnode = lnode-->pointers[0];
- if(tnode && (COMPARE(tnode-->korl.key, searchkey) == 0))
- {
- lnode = searchlist;
- for(i = 0; i < lnode-->korl.level; ++i)
- {
- if(tempptrs[i]-->pointers[i] != tnode)
- break;
- tempptrs[i]-->pointers[i] = tnode-->pointers[i];
- }
- free(tnode);
- while((i = lnode-->korl.level) > 1
- && lnode-->pointers[i] == NIL)
- {
- lnode-->korl.level = ----i;
- }
- return(TRUE);
- }
- else
- return(FALSE);
- }
-
- NODE *newnode(nlevel, nkey)
- int nlevel;
- KEYTYPE nkey;
-
- {
- #ifdef SLTEST
- extern jmp_buf errbuf;
- extern int *emptr;
- extern int nnodes;
- extern int levels[];
- #endif
- NODE *temp;
-
- temp = (NODE *)malloc(sizeof(NODE)+
- sizeof(NODE *) * (nlevel - 1));
- #ifdef SLTEST
- if(temp == NIL)
- longjmp(errbuf, NOMEM); /* best we can do */
- emptr[nnodes] = nkey; /* record keys as entered */
- nnodes += 1; /* track number allocated */
- levels[nlevel - 1] += 1; /* track distribution */
- #endif
- temp->korl.key = nkey; /* NOTE!!! no error checking */
- return(temp);
- }
-
- int randomlevel();
- {
- int slrandom();
- int newlevel;
-
-
- /*
- * NMAX/DMAX constitute a ratio n/d, such that (d-n)/d
- * of all nodes will have only 1 pointer and n/d of all
- * nodes with pointers at any level N will also have
- * pointers at level N+1
- */
-
- newlevel = 1;
- while(slrandom(DMAX) < NMAX)
- newlevel += 1;
- if(newlevel > DMAX)
- return(DMAX);
- else
- return(newlevel);
- }
-
-
- Listing 3 (random.c)
- /* random.c */
-
- #define SL_RAND_MAX 32767
-
- static long next = 1;
-
- unsigned slrand()
- {
- next = next * 1103515245 + 12345;
- return((unsigned int) ((next / 65536) % 32768));
- }
-
- slsrand(seed)
- unsigned seed;
- {
- next = seed;
- }
-
- /* return a random number in the range 0 ..(limit - 1)*/
- int slrandom(limit)
- unsigned limit;
- {
- long num;
-
- num = (long) slrand();
- return((int) ((num * limit) / (1L + SL_RAND_MAX)));
- }
-
-
- Listing 4 (sltest.c)
- /* sltest.c */
-
- #define SLTEST 1
- #include <stdio.h>
- #include <stdlib.h>
- #include <setjmp.h>
- #include "skiplist.h"
-
- /* globals */
-
- int listsize; /* size of our test list */
-
- int maxlevel = MAXLEVEL;
- int partition;
- int *emptr; /* dynamic array of list elements */
- int nnodes; /* count of nodes allocated */
- long ctotals; /* total number of comparisons */
- int comps; /* temporary counter */
- int mincomps;
- int maxcomps;
- #define COMPMAX 80 /* width of screen */
- int compary[COMPMAX] = { 0 }; /* for graph routine */
- int overflow = 0;
- int levels[MAXLEVEL] = { 0 };
- jmp_buf errbuf; /* short circuit when out of memory */
- NODE *testlist;
- int orderflag; /* non-zero for random insertions */
- #define SL_RAND_MAX 32767
-
- int intcmp(a, b) /* test comparison routine */
- KEYTYPE a, b;
- {
- comps += 1; /* track length of search path */
- if(a < b)
- return(-1);
- else if(a == b)
- return(0);
- else
- return(1);
- }
-
- main(argc, argv)
- int argc;
- char *argv[];
- {
- if(argc < 6)
- usage();
- if((listsize = atoi(argv[i])) < 1)
- usage();
- if(listsize > SL_RAND_MAX)
- {
- listsize = SL_RAND_MAX;
- printf("listsize reduced to %d\n", SL_RAND_MAX);
- }
- if((maxlevel = atoi(argv[2])) < 1)
- usage();
- if(maxlevel > MAXLEVEL)
- {
- maxlevel = MAXLEVEL;
- printf("maxlevel reduced to %d\n", MAXLEVEL);
- }
- if((partition = atoi(argv[3])) < 1)
- (partition >= maxlevel))
- {
- usage();
- }
- slsrand((unsigned) atoi(argv[4]));
- orderflag = atoi(argv[5]);
-
- list_init();
- list_build();
-
- list_test();
- list_stats();
- exit(EXIT_SUCCESS);
- }
-
- usage() /* describe command line errors */
- {
- printf("\nUsage:\t");
- printf("sltest <size> <maxlevel> <partition> <seed> <flag>\n");
- printf("\t<size> of test list (>= 1)\n");
- printf("\t<maxlevel> max number of pointers per node (>= 1)\n");
- printf("\t<partition> (>= 1 && < maxlevel [= %d])\n", maxlevel);
- printf("\t<seed> for the random number generator\n");
- printf("\t<flag> 0 for in-order insertions, otherwise random");
- exit(EXIT_FAILURE);
- }
-
- list_init()
- {
- if((emptr = (int *) calloc(listsize, sizeof(int)))
- == NULL)
- {
- nomem();
- }
- if((testlist = newlist()) == NIL)
- nomem();
- }
-
- nomem()
- {
- printf("Not enough memory for a list of size %u\n",
- (unsigned)listsize);
- exit(EXIT_FAILURE);
- }
-
- list_build()
- {
- unsigned slrand();
- unsigned i;
-
- if(setjmp(errbuf))
- listsize = nnodes; /* largest possible */
- else if(orderflag == 0)
- {
- for(nnodes = 0, i = 1; i <= listsize; )
- insert(testlist, i++);
- }
- else
- {
- for(nnodes = 0; nnodes < listsize; )
- insert(testlist, slrand());
- }
- }
-
- list_test()
- {
- int i;
-
- ctotals = 0L;
-
- mincomps = listsize;
- maxcomps = 0;
- for(i = 0, comps = 0; i < listsize; ++i, comps = 0)
- {
- search(testlist, emptr[i]);
- ctotals += (long) comps;
- if(comps < mincomps)
- mincomps = comps;
- else if(comps > maxcomps)
- maxcomps = comps;
- if(comps < COMPMAX)
- compary[comps] += 1;
- else
- overflow += 1; /* won't fit on screen */
- }
- }
-
- /* for computing behavior of balanced trees */
- unsigned treelevels[] = {
- 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
- 11, 12, 13, 14, 15, 16 };
- unsigned powersof2[] = {
- 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048,
- 4096, 8192, 16384, (SL_RAND_MAX + 1) };
- int depth;
- long accumulator;
-
- list_stats() /* print out statistics */
- {
- int i;
- int scale;
- int temp;
- int cmax;
-
- printf("\nskiplist size = %d", listsize);
- printf("\npointers per level = ");
- for(i = 0; i < maxlevel; i += 1)
- printf("%d ", levels[i]);
- printf("\ncomparisons per search: ");
- printf("mean = %.3f, minimum = %d, maximum = %d\n",
- ((float)ctotals) / ((float)listsize),
- mincomps, maxcomps);
- if(overflow != 0) /* graph won't fit on screen */
- printf("distribution of comparisons overflows buffer\n");
- else
- {
- cmax = 0;
- for(i = mincomps; i <= maxcomps; i += 1)
- {
- if(compary[i] > cmax)
- cmax = compary[i];
- }
- scale = cmax / 10;
- if(scale == 0) /* watch out for cmax < 10!! */
- scale = 1;
- temp = cmax % 10;
- if((temp / scale) > 5)
- scale += 1;
- for(temp = cmax; temp >= 0; temp -= scale)
-
- {
- if(temp <= scale) /* last pass through */
- if(scale > 1) /* make sure we */
- temp = 1; /* print all */
- for(i = mincomps;i <= maxcomps; i += 1)
- {
- if(compary[i] /temp)
- printf("*");
- else
- printf(" ");
- }
- printf("\n");
- }
- printf("distribution from minimum through maximum:");
- printf(" scale = %d:1\n", scale);
- }
- for(i = 0; (powersof2[i] - 1) < listsize; i += 1)
- ;
- depth = i--; /* maximum for perfectly balanced tree */
- accumulator = ((long) treelevels[i])
- * ((listsize - powersof2[i--]) + 1);
- white(i >= 0)
- {
- accumulator += (long) (treelevels[i]
- * powersof2[i--]);
- }
- printf("for perfect trees: ");
- printf("mean = %.3f, minimum = 1, maximum = %d/n",
- (((float)accumulator) / ((float)listsize)), depth);
- printf("for degenerate trees: ");
- printf("mean = %.3f, minimum = 1, maximum = %d\n",
- ((float) (listsize + 1) / 2), listsize);
- }
-
- /* EOF */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Positioning Nodes For General Trees
-
-
- John Q. Walker
-
-
- John Q. Walker II manages one of the software development teams implementing
- the SNA communications protocols on OS/2. He joined IBM in 1978, where he was
- a developer and tester for the operating system software of the IBM System/38.
- In Research Triangle Park, NC, John has been an architect for the IBM
- Token-Ring Network, serving as co-editor of the IEEE 802.5 local area network
- standard in 1983 and 1984. John received a B.S., B.A., and M.S. from Southern
- Illinois University and is completing a Ph.D. in computer science at the
- University of North Carolina at Chapel Hill. You may contact John at IBM
- Corp., Dept. E42, Bldg. 673, P.O. Box 12195, Research Triangle Park, NC 27709.
-
-
- The bulk of this article first appeared in Software - Practice and Experience,
- July 1990, Copyright 1990 by John Wiley and Sons, Ltd. Reprinted by
- permission.
-
-
- Drawing a tree consists of two stages: determining the position of each node,
- and actually rendering the individuals nodes and interconnecting branches. The
- algorithm described here concerns the first stage. That is, given a list of
- nodes, an indication of the hierarchical relationship among them, and their
- shape and size, where should each node be positioned for optimal aesthetic
- effect?
- This algorithm determines the positions of the nodes for an arbitrary general
- tree. The positioning, specified in x, y coordinates, minimizes the width of
- the tree. A general tree may have an unlimited number of offspring per node.
- Binary and ternary trees, on the other hand, can have only two and three
- offspring per node, respectively. This algorithm operates in time 0(N), where
- N is the number of nodes in the tree.
- In textbooks, most tree drawings have been positioned by the sure hand of a
- human graphic designer. Many computer-generated positionings have been either
- trivial or contained irregularities. Earlier work by Wetherell and Shannon [8]
- and Tilford [6], upon which the algorithm builds, failed to correctly position
- the interior nodes of some trees. The algorithm in this article correctly
- positions a tree's nodes in two passes. It also handles several practical
- considerations: alternate orientations of the tree, variable node sizes, and
- out-of-bounds conditions. Radack [2], also building on Tilford's work, solved
- this same problem with an algorithm that makes four passes.
- A history of algorithms leading up to this one is presented in reference [7].
- This algorithm lets a computer reliably generate tree drawings equivalent to
- those done by a skilled human. Below are some of the applications that often
- use tree-drawings.
- Drawings of B-trees and 2-3 trees
- Structure editors that draw trees
- Flow charts without loops
- Visual LISP editors
- Parse trees
- Decision trees
- Hierarchical database models
- Hierarchically-organized file systems (for example, directories,
- subdirectories, and files)
- Organizational charts
- Tables of contents in printed matter
- Biological classifications
-
-
- General Trees
-
-
- This paper deals with rooted, directed trees, that is, trees with one root and
- hierarchical connections from the root to its offspring. No node may have more
- than one parent.
- Each node in a general tree can have an unlimited number of offspring. A
- general tree is also known as an m-ary tree, since each node can have m
- offspring (where m is 0 or more). As a class, binary trees differ from general
- trees in that an offspring of a binary tree node must be either the left
- offspring or the right offspring. Binary tree drawings preserve this
- left-right distinction. Thus, a single offspring is placed under its parent
- node either to the left or right of its parent's position. This left-right
- distinction does not apply in a general tree. If a node has a single
- offspring, the offspring is placed directly below its parent.
- This algorithm positions a binary tree by ignoring the left-right distinction.
- If a node has exactly one offspring, it is positioned directly below its
- parent. Supowit and Reingold [4] noted that it is NP-hard to optimally
- position minimum-width binary trees (while adhering to the left-right
- distinction) to within a factor of less than about four percent.
-
-
- Aesthetic Rules
-
-
- Wetherell and Shannon [8] first described a set of aesthetic rules against
- which a good positioning algorithm must be judged. Tilford [6] and Supowit and
- Reingold [4] expanded that list in an effort to produce better algorithms.
- A tidy tree drawing occupies as little space as possible while satisfying
- certain aesthetics:
- 1. Nodes at the same level of the tree should lie along a straight line, and
- the straight lines defining the levels should be parallel [8].
- 2. A parent should be centered over its offspring [8].
- 3. A tree and its mirror image should produce drawings that are reflections of
- one another. Moreover, a subtree should be drawn the same way regardless of
- where it occurs in the tree. In some applications, one wishes to examine large
- trees to find repeated patterns. Searching for patterns is facilitated by
- having isomorphic subtrees drawn isomorphically [3].
- This implies that small subtrees should not appear arbitrarily positioned
- among larger subtrees. Small, interior subtrees should be spaced out evenly
- among larger subtrees (where the larger subtrees are adjacent at one or more
- levels). Small subtrees at the far left or far right should be adjacent to
- larger subtrees.
- The algorithm described in this article satisfies these aesthetic rules.
-
-
- How The Algorithm Works
-
-
- This algorithm initially assumes that the root of the tree is at the top of
- the drawing [1]. Node-positioning algorithms are concerned only with
- determining the x-coordinates of the nodes. The y-coordinate can be determined
- from its level in the tree, due to Aesthetic 1 and the convention of providing
- a uniform vertical separation between consecutive levels. A later section
- presents a variation for placing the root in other positions.
- This algorithm uses two positioning concepts, the first of which is building
- subtrees as rigid units. Moving a node moves all of its descendants (if it has
- any) -- the entire subtree is thus treated as a rigid unit. A general tree is
- positioned by building it up recursively from its leaves toward its root.
- Second is the concept of using two fields for the positioning of each node.
- They are a preliminary x-coordinate, and a modifier field.
- Two tree traversals are used to produce a node's final x-coordinate. The first
- traversal assigns the preliminary x-coordinate and modifier fields for each
- node. The second traversal computes the final x-coordinate of each node by
- summing the node's preliminary x-coordinate with the modifier fields of all of
- its ancestors. This technique makes moving a large subtree simple and allows
- the algorithm to operate in time 0(N).
-
- For example, to move a subtree four units to the right, increment both the
- preliminary x-coordinate and the modifier field of the subtree's root by four.
- As another example, the modifier field associated with the tree's apex node is
- used in determining the final position of all of its descendants. (I use the
- term apex node to distinguish the root of the entire tree from the roots of
- individual internal subtrees.)
- The first tree traversal is a postorder traversal, positioning the smallest
- subtrees (the leaves) first and recursively proceeding from left to right to
- build up the position of larger and larger subtrees. Sibling nodes are always
- separated from one another by a predefined minimal distance (the sibling
- separation). Adjacent subtrees are separated by at least a predefined subtree
- separation. Subtrees of a node are formed independently and placed as close
- together as these separation values allow.
- As the tree walk moves from the leaves to the apex, it combines smaller
- subtrees and their root to form a larger subtree. For a given node, its
- subtrees are positioned one-by-one, moving left to right. Imagine that its
- newest subtree has been drawn and cut out of paper along its contour. Now
- superimpose the new subtree atop its neighbor to the left, and move them apart
- until no two points touch. Initially the subtrees' roots are separated by the
- sibling separation value. The roots are then pushed apart until the adjacent
- subtrees at the lower level are separated by the subtree separation value.
- This process continues at successively lower levels until it arrives at the
- bottom of the shorter subtree. Note that the new subtree being superimposed
- may not always bump against a descendant of its nearest sibling to the left.
- Siblings much farther to the left, but with many offspring, may cause the new
- subtree to be pushed to the right. At some levels no movement may be
- necessary, but at no level are subtrees moved closer together. When this
- process is complete for all of the offspring of a node, the node is centered
- over its leftmost and rightmost offspring.
- Pushing a new, large subtree farther and farther to the right may open a gap
- between the large subtree and smaller subtrees that had been positioned
- correctly, but are now bunched on the left with an empty area to their right.
- This undesirable left-to-right gluing was the failing of the algorithms by
- Sweet, Wetherell and Shannon, and Tilford.
- The algorithm presented here produces evenly distributed, proportional spacing
- among subtrees. The distance that a large subtree is moved is apportioned to
- smaller, interior subtrees, satisfying Aesthetic 3. Moving these subtrees is
- accomplished as before -- by adding the proportional values to the preliminary
- x-coordinate and modifier fields of the roots of the small interior subtrees.
- For example, if three small subtrees are bunched at the left because a new
- large subtree has been positioned to the right, the first small subtree is
- shifted right by one-fourth of the gap, the second small subtree is shifted
- right by one-half of the gap, and the third small subtree is shifted right by
- three-quarters of the gap.
- The second tree traversal, a preorder traversal, determines the final
- x-coordinate for each node. It starts at the apex node and sums each node's
- x-coordinate value with the combined sum of the modifier fields of its
- ancestors. The second traversal also adds a value that guarantees centering
- the display with respect to the position of the apex node.
-
-
- Changing Root Orientation
-
-
- The algorithm allows tree positionings where the apex is not at the top of the
- drawing. One such orientation puts the root on the left and the siblings to
- its right. Four such orientations of the root are the values taken by the
- global constant RootOrientation, set before the algorithm is called.
- NORTH -- root is at the top, as in the preceding algorithm
- SOUTH -- root is at the bottom, its siblings are above it
- EAST -- root is at the right, its siblings are to its left
- WEST -- root is at the left, its siblings are to its right
-
-
- An Extended Example
-
-
- The algorithm's operation during the two walks can be best illustrated with an
- example (see Figure 1). At least three levels are needed to illustrate its
- operation, since a small subtree must be centered between larger sibling
- subtrees. The following example has fifteen nodes lettered in the order that
- they are visited in the first, postorder traversal. For this example, the mean
- size of each node is two units and the sibling separation and subtree
- separation values are four units.
- In the postorder walk, nodes' preliminary x-coordinate value and modifier
- values are calculated.
- A is a leaf with no left sibling.
- PRELIM(A) = 0
- MODIFIER(A) = 0
- B is also a leaf with no left sibling.
- PRELIM(B) = 0
- MODIFIER(B) = 0
- C is the right sibling of node B and is separated from node B by the sibling
- separation value plus the mean size of the two nodes.
- PRELIM(C) = 0 + 4 + 2 = 6
- MODIFIER(C) = 0
- D is the parent of nodes B and C, and the right sibling of node A. D is
- separated from node A by the sibling separation value plus the mean size of
- the two nodes. D's modifier value is set so that applying it to nodes B and C
- will center them under D. The modifier is determined by subtracting the mean
- of the PRELIM (its mostly widely-separated offspring) values from PRELIM(D).
- PRELIM(D) = 0 + 4 + 2 = 6
- MODIFIER(D) = 6 - (0 + 6)/2 = 3
- E is the parent of nodes A and D, and is centered over nodes A and D.
- PRELIM(E) = (0 + 6)/2 = 3
- MODIFIER(E) = 0
- F is a right sibling of node E, and is separated from E by the sibling
- separation value plus the mean size of the two nodes. That would place it
- directly over node C. You can see now that node N's subtree will later be
- placed much further to the right, leaving the spacing between nodes E and F
- smaller, and hence different, than the spacing between nodes F and N. When
- node N is finally positioned, the position of node F will be adjusted. But for
- now,
- PRELIM(F) = 3 + 4 + 2 = 9
- MODIFIER(F) = 0
- G and H are leaves with no left sibling.
- PRELIM(G) = 0
- MODIFIER(G) = 0
-
- PRELIM(H) = 0
- MODIFIER(H) = 0
- I is the right sibling of node H. It is separated from H by the sibling
- separation value plus the mean size of the two nodes.
- PRELIM(I) = 0 + 4 + 2 = 6
- MODIFIER(I) = 0
- J is the right sibling of node I. As before, it is separated by the standard
- spacing from node I.
- PRELIM(J) = 6 + 4 + 2 = 12
- MODIFIER(J) = 0
- K is the right sibling of node J, and L is the right sibling of node K.
- PRELIM(K) = 12 + 4 + 2 = 18
- MODIFIER(K) = 0
-
- PRELIM(L) = 18 + 4 + 2 = 24
- MODIFIER(L) = 0
-
- M is the parent of nodes H through L, and is the right sibling of node G. It
- is separated from node G by the sibling separation value plus the mean size of
- the two nodes. Its modifier is set so that when it is applied to nodes H
- through L, they will appear centered underneath it.
- PRELIM(M) = 0 + 4 + 2 = 6
- MODIFIER(M) = 6 - (0 + 24)/2 = -6
- N is the parent of nodes G and M, and is the right sibling of node F. N first
- receives its standard positioning to the right of node F, with a modifier that
- reflects the centering of its offspring beneath it.
- PRELIM(N) = 9 + 4 + 2 = 15
- MODIFIER(N) = 15 - (0 + 6)/2 = 12
- Now verify that node E's subtree and node N's subtree are properly separated.
- Move down one level. The leftmost descendant of node N, node G, currently has
- a positioning of 0 + 12 = 12 (PRELIM(G) plus the MODIFIER(N), its parent). The
- rightmost descendant of node E, node D is positioned at 6 + 0 = 6 (PRELIM(D)
- plus the MODIFIER(E), its parent). Their difference is 12 - 6 = 6, which is
- equal to the minimum separation (subtree separation plus mean node size), so
- N's subtree does not need to be moved, since there is no overlap at this
- level.
- Move down another level. The leftmost descendant of node N is node H. It is
- positioned at 0 + -6 + 12 = 6 (PRELIM(H) plus MODIFIER(M) and MODIFIER(N)).
- The rightmost descendant of node E, node C, is positioned at 6 + 3 + 0 = 9
- (PRELIM(C) plus MODIFIER(D) and MODIFIER(E)). Their difference is 6 - 9 = -3;
- it should be 6, the minimum subtree separation plus the mean node size. Thus
- node N and its subtree need to be moved to the right a distance of 6 - -3 = 9.
- PRELIM(N) = 15 + 9 = 24
- MODIFIER(N) = 12 + 9 = 21
- Moving node N and its subtree opens a gap of size nine between sibling nodes E
- and N. This difference must be evenly distributed to all contained sibling
- nodes, and node F is the only one. It is moved to the right a distance of 9/2
- = 4.5.
- PRELIM(F) = 9 + 4.5 = 13.5
- MODIFIER(F) = 0 + 4.5 = 4.5
- 0 is the parent of nodes E, F, and N. It is positioned halfway between the
- position of nodes E and N.
- PRELIM(0) = (3 + 24)/2 = 13.5
- MODIFIER(0) = 0
- All the nodes are visited a second time in a preorder traversal. Their final
- x-coordinates are determined by summing their preliminary x-coordinates with
- the modifier fields of all of their ancestors. (See Table 1.)
-
-
- The Algorithm
-
-
- Since the algorithm operates by making two recursive walks of the tree,
- several variables are global for the sake of runtime efficiency. These
- variables are described below, alphabetically. All other variables are local
- to their respective procedures and functions.
- pLevelZero: The algorithm maintains a list containing the previous node at
- each level, that is, the adjacent neighbor to the left. pLevelZero is a
- pointer to the first entry in this list.
- xTopAdjustment: A fixed distance used in the final walk of the tree to
- determine the absolute x-coordinate of a node with respect to the apex node of
- the tree.
- yTopAdjustment: A fixed distance used in the final walk of the tree to
- determine the absolute y-coordinate of a node with respect to the apex node of
- the tree.
- The following global constants must be set before the algorithm is called.
- LevelSeparation: The fixed distance between adjacent levels of the tree. Used
- in determining the y-coordinate of a node being positioned.
- MaximumDepth: The maximum number of levels in the tree to be positioned. If
- all levels are to be positioned, set this value to positive infinity (or an
- appropriate numerical value).
- SiblingSeparation: The minimum distance between adjacent siblings of the tree.
- SubtreeSeparation: The minimum distance between adjacent subtrees of a tree.
- For proper aesthetics, this value is normally somewhat larger than
- SiblingSeparation.
- Upon entry to function TreePosition, four hierarchical relationships are
- required for each node. Also, the X and Y coordinates of the apex node are
- required. Upon its successful completion, the algorithm has set the X and Y
- coordinate values for each node in the tree.
- The algorithm is invoked by calling function TreePosition, passing it a
- pointer to the apex node of the tree. If the tree is too wide or too tall to
- be positioned within the coordinate system being used, TreePosition returns
- the boolean FALSE; otherwise it returns TRUE.
- I use the internal tree notation described by Knuth [Reference 1, section
- 2.3.3] for a triply-linked tree. Each node consists of three pointers,
- *parent, *offspring, and *rightsibling, and its information in field *info.
- Thus, if node T is the root of a binary tree, the root of its left subtree is
- T->offspring
- and the root of its right subtree is
- T->offspring->rightsibling
-
-
- Acknowledgements
-
-
- I appreciate the written correspondence I received from the experts on this
- topic (listed alphabetically): Brad A. Myers at the Computer Systems Research
- Institute in Toronto, Andy Poggio at SRI International, Edward Reingold at the
- University of Illinois, Bob Tarjan at AT&T Bell Laboratories, and C.S.
- Wetherell at AT&T Information Systems. Thanks also to Jane Munn, Jim Staton,
- Bob Gibson, John Broughton III, Steve Joyce, and Dr. Bill Wright at IBM. The
- bulk of this article first appeared in Software -- Practice and Experience,
- July 1990, Copyright 1990 by John Wiley and Sons, Ltd. Reprinted by
- permisison.
- References
- [1] Knuth, D.E., The Art of Computer Programming, Volume 1: Fundamental
- Algorithms, Addison-Wesley, Reading, MA, 1973.
- [2] Radack, G.M., "Tidy Drawing of M-ary Trees," Department of Computer
- Engineering and Science, Case Western Reserve University, Cleveland, Ohio,
- Report CES-88-24, November 1988.
- [3] Reingold, E.M. and J.S. Tilford., "Tidier Drawings of Trees," IEEE
- Transactions on Software Engineering SE-7, 2, March 1981, 223-228.
- [4] Supowit, K.J. and E.M. Reingold, "The complexity of drawing trees nicely,"
- Acta Informatica 18, 4, January 1983, 377-392.
- [5] Sweet, R.E., "Empirical estimates of program entropy." Department of
- Computer Science, Stanford University, Stanford, CA, Report STAN-CS-78-698,
- November 1978. Also issued as Report CSL-78-3, Xerox PARC, Palo Alto, CA,
- September 1978.
- [6] Tilford, J.S., "Tree drawing algorithms." M.S. Thesis, Department of
- Computer Science, University of Illinois, Urbana, IL, Report
- UIUCDCS-R-81-1055, April 1981. Available as document UILU-ENG-81-1711 from the
- College of Engineering Document Center at the Univ. of Illinois.
- [7] Walker II, J. Q., "A Node-Positioning Algorithm for General Trees,"
- Software -- Practice and Experience 10, 1980 553-561.
- [8] Wetherell, C.S. and A. Shannon, "Tidy Drawings of Trees," IEEE
- Transactions on Software Engineering, SE-5, 5 September 1979, 514-520.
- Figure 1
- Table 1
- Node Final X-coordinate
- (preliminary x-coordinate
- + modifiers of ancestors)
-
-
- O 13.5
- E 3 + 0 = 3
- A 0 + 0 + 0 = 0
- D 6 + 0 + 0 = 6
- B 0 + 3 + 0 + 0 = 3
- C 6 + 3 + 0 + 0 = 9
- F 13.5 + 0 = 13.5
- N 24 + 0 = 24
- G 0 + 21 + 0 = 21
- M 6 + 21 + 0 = 27
- H 0 + -6 + 21 + 0 = 15
- I 6 + -6 + 21 + 0 = 21
- J 12 + -6 + 21 + 0 = 27
- K 18 + -6 + 21 + 0 = 33
- L 24 + -6 + 21 + 0 = 39
-
- Listing 1
- /***********************************************************
- * Node-Positioning for General Trees, by John Q. Walker II
- *
- * Initiated by calling procedure TreePosition().
- **********************************************************/
-
- #include <stdlib.h>
-
- /*----------------------------------------------------------
- * Implementation dependent: Set the values for each of
- * these variables.
- *--------------------------------------------------------*/
- #define NODE_WIDTH 20 /* Width of a node? */
- #define NODE_HEIGHT 10 /* Height of a node? */
- #define FRAME_THICKNESS 1 /* Fixed-sized node frame?*/
- #define SUBTREE_SEPARATION 5 /* Gap between subtrees? */
- #define SIBLING_SEPARATION 4 /* Gap between siblings? */
- #define LEVEL_SEPARATION 5 /* Gap between levels? */
- #define MAXIMUM_DEPTH 10 /* Biggest tree? */
-
- /*----------------------------------------------------------
- * Implementation dependent: The structure for one node
- * - The first 4 pointers must be set for each node before
- * this algorithm is called.
- * - The X and Y coordinates must be set for only the apex
- * of the tree upon entry; they will be set by this
- * algorithm for all the other nodes.
- * - The next three elements are used only for the duration
- * of the algorithm.
- * - Actual contents of the node depend on your application.
- *--------------------------------------------------------*/
- typedef int COORD; /* X,Y coordinate type */
-
- typedef struct node {
- struct node *parent; /* ptr: node's parent */
- struct node *offspring; /* ptr: leftmost child */
- struct node *leftsibling; /* ptr: sibling on left */
- struct node *rightsibling; /* ptr: sibling on right */
- COORD xCoordinate; /* node's current x coord */
- COORD yCoordinate; /* node's current y coord */
-
- struct node *prev; /* ptr: lefthand neighbor */
-
- float flPrelim; /* preliminary x coord */
- float flModifier; /* temporary modifier */
-
- char info[80]; /* pick your contents! */
- } *PNODE; /* ptr: a node structure */
-
- /*----------------------------------------------------------
- * Global variables used by the algorithm
- *--------------------------------------------------------*/
- typedef enum { FALSE, TRUE } BOOL;
- typedef enum { FRAME, NO_FRAME } FRAME_TYPE;
- typedef enum { NORTH, SOUTH, EAST, WEST } ROOT_ORIENTATION;
-
- typedef struct prev_node { /* one list element */
- PNODE pPreviousNode; /* ptr: previous at level */
- struct prev_node *pNextLevel; /* ptr: next element */
- } *PPREVIOUS_NODE;
-
- static FRAME_TYPE FrameType = FRAME; /* Show a frame */
- static ROOT_ORIENTATION RootOrientation = NORTH; /* At top*/
- static PPREVIOUS_NODE pLevelZero = (PPREVIOUS_NODE)0;
-
- static COORD xTopAdjustment; /* How to adjust the apex */
- static COORD yTopAdjustment; /* How to adjust the apex */
- static float flMeanWidth; /* Ave. width of 2 nodes */
-
- #define FIRST_TIME (0) /* recursive proc flag */
-
- /*----------------------------------------------------------
- * Implemented as macros, but could be implemented as
- * procedures depending on your particular node structures
- *--------------------------------------------------------*/
-
- #define FirstChild(node) ((PNODE)((node)->offspring))
- #define LeftSibling(node) ((PNODE)((node)->leftsibling))
- #define RightSibling(node) ((PNODE)((node)->rightsibling))
- #define Parent(node) ((PNODE)((node)->parent))
- #define LeftNeighbor(node) ((PNODE)((node)->prev))
- #define IsLeaf(node) \
- (((node)&&(!((node)->offspring)))?TRUE:FALSE)
- #define HasChild(node) \
- (((node)&&((node)->offspring))?TRUE:FALSE)
- #define HasLeftSibling(node) \
- (((node)&&((node)->leftsibling))?TRUE:FALSE)
- #define HasRightSibling(node) \
- (((node)&&((node)->rightsibling))?TRUE:FALSE)
-
- static PNODE GetPrevNodeAtLevel (unsigned nLevelNbr)
- {
- /*------------------------------------------------------
- * List Manipulation: Return pointer to previous node at
- * this level
- *----------------------------------------------------*/
- PPREVIOUS_NODE pTempNode; /* used in the for-loop */
- unsigned i = 0; /* level counter */
-
- for (pTempNode = pLevelZero; (pTempNode);
- pTempNode = pTempNode->pNextLevel) {
- if (i++ == nLevelNbr)
-
- /* Reached desired level. Return its pointer */
- return (pTempNode->pPreviousNode);
- }
- return ((PNODE)0); /* No pointer yet for this level. */
- }
-
- static BOOL SetPrevNodeAtLevel (unsigned nLevelNbr,
- PNODE pThisNode)
- {
-
- /*------------------------------------------------------
- * List Manipulation: Set the list element to the
- * previous node at this level
- *----------------------------------------------------*/
-
- PPREVIOUS_NODE pTempNode; /* used in the for-loop */
- PPREVIOUS_NODE pNewNode; /* newly-allocated memory */
- unsigned i = 0; /* level counter */
-
- for (pTempNode = pLevelZero; (pTempNode);
- pTempNode = pTempNode->pNextLevel) {
- if (i++ == nLevelNbr) {
- /* Reached desired level. Return its pointer */
- pTempNode->pPreviousNode = pThisNode;
- return (TRUE);
- }
- else if (pTempNode->pNextLevel==(PPREVIOUS_NODE)0) {
- /* Looks like we need a new level: add it. */
- /* We need to keep going--should be the next. */
- pNewNode = (PPREVIOUS_NODE)
- malloc(sizeof(struct prev_node));
- if (pNewNode) {
- pNewNode->pPreviousNode = (PNODE)0;
- pNewNode->pNextLevel = (PPREVIOUS_NODE)0;
- pTempNode->pNextLevel = pNewNode;
- }
- else return (FALSE); /* The malloc failed. */
- }
- }
-
- /* Should only get here if pLevelZero is 0. */
- pLevelZero = (PPREVIOUS_NODE)
- malloc(sizeof(struct prev_node));
- if (pLevelZero) {
- pLevelZero->pPreviousNode = pThisNode;
- pLevelZero->pNextLevel = (PPREVIOUS_NODE)0;
- return (TRUE);
- }
- else return (FALSE); /* The malloc() failed. */
- }
-
- static void InitPrevNodeAtLevel (void)
- {
- /*------------------------------------------------------
- * List Manipulation: Initialize the list of the
- * previous node at each level to all zeros.
- *----------------------------------------------------*/
-
- PPREVIOUS_NODE pTempNode = pLevelZero; /* the start */
-
-
- for ( ; (pTempNode); pTempNode = pTempNode->pNextLevel)
- pTempNode->pPreviousNode = (PNODE)0;
- }
-
- static BOOL CheckExtentsRange(long lxTemp, long lyTemp)
- {
- /*-----------------------------------------------------
- * Insert your own code here, to check when the
- * tree drawing is going to be too big. My region is no
- * more that 64000 units square.
- *---------------------------------------------------*/
-
- if ((labs(lxTemp) > 32000) (labs(lyTemp) > 32000))
- return (FALSE);
- else
- return (TRUE);
- }
-
- static void TreeMeanNodeSize (PNODE pLeftNode,
- PNODE pRightNode)
- {
- /*------------------------------------------------------
- * Write your own code for this procedure if your
- * rendered nodes will have variable sizes.
- *------------------------------------------------------
- * Here I add the width of the contents of the
- * right half of the pLeftNode to the left half of the
- * right node. Since the size of the contents for all
- * nodes is currently the same, this module computes the
- * following trivial computation.
- *----------------------------------------------------*/
-
- flMeanWidth = (float)0.0; /* Initialize this global */
-
- switch (RootOrientation) {
- case NORTH:
- case SOUTH:
- if (pLeftNode) {
- flMeanWidth = flMeanWidth +
- (float)(NODE_WIDTH/2);
- if (FrameType != NO_FRAME)
- flMeanWidth = flMeanWidth +
- (float)FRAME_THICKNESS;
- }
- if (pRightNode) {
- flMeanWidth = flMeanWidth +
- (float)(NODE_WIDTH/2);
- if (FrameType != NO_FRAME)
- flMeanWidth = flMeanWidth +
- (float)FRAME_THICKNESS;
- }
- break;
- case EAST :
- case WEST :
- if (pLeftNode) {
- flMeanWidth = flMeanWidth +
- (float)(NODE_HEIGHT/2);
- if (FrameType != NO_FRAME)
-
- flMeanWidth = flMeanWidth +
- (float)FRAME_THICKNESS;
- }
- if (pRightNode) {
- flMeanWidth = flMeanWidth +
- (float)(NODE_HEIGHT/2);
- if (FrameType != NO_FRAME)
- flMeanWidth = flMeanWidth +
- (float)FRAME_THICKNESS;
- }
- break;
- }
- }
-
- static PNODE TreeGetLeftmost(PNODE pThisNode,
- unsigned nCurrentLevel,
- unsigned nSearchDepth)
- {
- /*------------------------------------------------------
- * Determine the leftmost descendant of a node at a
- * given depth. This is implemented using a post-order
- * walk of the subtree under pThisNode, down to the
- * level of nSearchDepth. If we've searched to the
- * proper distance, return the currently leftmost node.
- * Otherwise, recursively look at the progressively
- * lower levels.
- *----------------------------------------------------*/
-
- PNODE pLeftmost; /* leftmost descendant at level */
- PNODE pRightmost; /* rightmost offspring in search */
-
- if (nCurrentLevel == nSearchDepth)
- pLeftmost = pThisNode; /* searched far enough. */
- else if (IsLeaf(pThisNode))
- pLeftmost = 0; /* This node has no descendants */
- else { /* Do a post-order walk of the subtree. */
- for (pLeftmost = TreeGetLeftmost(pRightmost =
- FirstChild(pThisNode),
- nCurrentLevel + 1,
- nSearchDepth)
-
- ;
- (pLeftmost==0) && (HasRightSibling(pRightmost))
- ;
- pLeftmost = TreeGetLeftmost(pRightmost =
- RightSibling(pRightmost),
- nCurrentLevel + 1,
- nSearchDepth)
- ) { /* Nothing inside this for-loop. */ }
- }
- return (pLeftmost);
- }
-
- static void TreeApportion (PNODE pThisNode,
- unsigned nCurrentLevel)
- {
- /*------------------------------------------------------
- * Clean up the positioning of small sibling subtrees.
- * Subtrees of a node are formed independently and
-
- * placed as close together as possible. By requiring
- * that the subtrees be rigid at the time they are put
- * together, we avoid the undesirable effects that can
- * accrue from positioning nodes rather than subtrees.
- *----------------------------------------------------*/
-
- PNODE pLeftmost; /* leftmost at given level*/
- PNODE pNeighbor; /* node left of pLeftmost */
- PNODE pAncestorLeftmost; /* ancestor of pLeftmost */
- PNODE pAncestorNeighbor; /* ancestor of pNeighbor */
- PNODE pTempPtr; /* loop control pointer */
- unsigned i; /* loop control */
- unsigned nCompareDepth; /* depth of comparison */
- /* within this proc */
- unsigned nDepthToStop; /* depth to halt */
- unsigned nLeftSiblings; /* nbr of siblings to the */
- /* left of pThisNode, including pThisNode, */
- /* til the ancestor of pNeighbor */
- float flLeftModsum; /* sum of ancestral mods */
- float flRightModsum; /* sum of ancestral mods */
- float flDistance; /* difference between */
- /* where pNeighbor thinks pLeftmost should be */
- /* and where pLeftmost actually is */
- float flPortion; /* proportion of */
- /* flDistance to be added to each sibling */
-
- pLeftmost = FirstChild(pThisNode);
- pNeighbor = LeftNeighbor(pLeftmost);
-
- nCompareDepth = 1;
- nDepthToStop = MAXIMUM_DEPTH - nCurrentLevel;
-
- while ((pLeftmost) && (pNeighbor) &&
- (nCompareDepth <= nDepthToStop)) {
-
- /* Compute the location of pLeftmost and where it */
- /* should be with respect to pNeighbor. */
- flRightModsum = flLeftModsum = (float)0.0;
- pAncestorLeftmost = pLeftmost;
- pAncestorNeighbor = pNeighbor;
- for (i = 0; (i < nCompareDepth); i++) {
- pAncestorLeftmost = Parent(pAncestorLeftmost);
- pAncestorNeighbor = Parent(pAncestorNeighbor);
- flRightModsum = flRightModsum +
- pAncestorLeftmost->flModifier;
- flLeftModsum = flLeftModsum +
- pAncestorNeighbor->flModifier;
-
- }
-
- /* Determine the flDistance to be moved, and apply*/
- /* it to "pThisNode's" subtree. Apply appropriate*/
- /* portions to smaller interior subtrees */
-
- /* Set the global mean width of these two nodes */
- TreeMeanNodeSize(pLeftmost, pNeighbor);
-
- flDistance = (pNeighbor->flPrelim +
- flLeftModsum +
-
- (float)SUBTREE_SEPARATION +
- (float)flMeanWidth) -
- (pLeftmost->flPrelim + flRightModsum);
-
- if (flDistance > (float)0.0) {
- /* Count the interior sibling subtrees */
- nLeftSiblings = 0;
- for (pTempPtr = pThisNode;
- (pTempPtr) &&
- (pTempPtr != pAncestorNeighbor);
- pTempPtr = Leftsibling(pTempPtr)) {
- nLeftSiblings++;
- }
-
- if (pTempPtr) {
- /* Apply portions to appropriate */
- /* leftsibling subtrees. */
- flPortion = flDistance/(float)nLeftSiblings;
- for (pTempPtr = pThisNode;
- (pTempPtr != pAncestorNeighbor);
- pTempPtr = LeftSibling(pTempPtr)) {
- pTempPtr->flPrelim =
- pTempPtr->flPrelim + flDistance;
- pTempPtr->flModifier =
- pTempPtr->flModifier + flDistance;
- flDistance = flDistance - flPortion;
- }
- }
- else {
- /* Don't need to move anything--it needs */
- /* to be done by an ancestor because */
- /* pAncestorNeighbor and */
- /* pAncestorLeftmost are not siblings of */
- /* each other. */
- return;
- }
- } /* end of the while */
-
- /* Determine the leftmost descendant of pThisNode */
- /* at the next lower level to compare its */
- /* positioning against that of its pNeighbor. */
-
- nCompareDepth++;
- if (IsLeaf(pLeftmost))
- pLeftmost = TreeGetLeftmost(pThisNode, 0,
- nCompareDepth);
- else
- pLeftmost = FirstChild(pLeftmost);
- pNeighbor = LeftNeighbor(pLeftmost);
- }
- }
-
- static BOOL TreeFirstWalk(PNODE pThisNode,
- unsigned nCurrentLevel)
- {
- /*------------------------------------------------------
- * In a first post-order walk, every node of the tree is
- * assigned a preliminary x-coordinate (held in field
- * node->flPrelim). In addition, internal nodes are
-
- * given modifiers, which will be used to move their
- * children to the right (held in field
- * node->flModifier).
- * Returns: TRUE if no errors, otherwise returns FALSE.
- *----------------------------------------------------*/
-
- PNODE pLeftmost; /* left- & rightmost */
- PNODE pRightmost; /* children of a node. */
- float flMidpoint; /* midpoint between left- */
- /* & rightmost children */
-
- /* Set up the pointer to previous node at this level */
- pThisNode->prev = GetPrevNodeAtLevel(nCurrentLevel);
-
- /* Now we're it--the previous node at this level */
- if (!(SetPrevNodeAtLevel(nCurrentLevel, pThisNode)))
- return (FALSE); /* Can't allocate element */
-
- /* Clean up old values in a node's flModifier */
- pThisNode->flModifier = (float)0.0;
-
- if ((IsLeaf(pThisNode))
- (nCurrentLevel == MAXIMUM_DEPTH)) {
- if (HasLeftSibling(pThisNode)) {
-
- /*--------------------------------------------
- * Determine the preliminary x-coordinate
- * based on:
- * - preliminary x-coordinate of left sibling,
- * - the separation between sibling nodes, and
- * - mean width of left sibling & current node.
- *--------------------------------------------*/
- /* Set the mean width of these two nodes */
- TreeMeanNodeSize(LeftSibling(pThisNode),
- pThisNode);
-
- pThisNode->flPrelim =
- (pThisNode->Leftsibling->flPrelim) +
- (float)SIBLING_SEPARATION +
- flMeanWidth;
- }
- else /* no sibling on the left to worry about */
- pThisNode->flPrelim = (float)0.0;
- }
- else {
- /* Position the leftmost of the children */
- if (TreeFirstWalk(pLeftmost = pRightmost =
- FirstChild(pThisNode),
- nCurrentLevel + 1)) {
- /* Position each of its siblings to its right */
- while (HasRightSibling(pRightmost)) {
- if (TreeFirstWalk(pRightmost =
- RightSibling(pRightmost),
- nCurrentLevel + 1)) {
- }
- else return (FALSE); /* malloc() failed */
- }
-
- /* Calculate the preliminary value between */
-
- /* the children at the far left and right */
- flMidpoint = (pLeftmost->flPrelim +
- pRightmost->flPrelim)/(2.0);
-
- /* Set global mean width of these two nodes */
- TreeMeanNodeSize(LeftSibling(pThisNode),
- pThisNode);
-
- if (HasLeftSibling(pThisNode)) {
- pThisNode->flPrelim =
- (pThisNode->leftsibling->flPreLim) +
- (float)SIBLING_SEPARATION +
- flMeanWidth;
- pThisNode->flModifier =
- pThisNode->flPrelim - flMidpoint;
- TreeApportion(pThisNode, nCurrentLevel);
- }
- else pThisNode->flPrelim = flMidpoint;
- }
- else return (FALSE); /* Couldn't get an element */
- }
- return (TRUE);
- }
-
- static BOOL TreeSecondWalk(PNODE pThisNode,
- unsigned nCurrentLevel)
- {
- /*------------------------------------------------------
- * During a second pre-order walk, each node is given a
- * final x-coordinate by summing its preliminary
- * x-coordinate and the modifiers of all the node's
- * ancestors. The y-coordinate depends on the height of
- * the tree. (The roles of x and y are reversed for
- * RootOrientations of EAST or WEST.)
- * Returns: TRUE if no errors, otherwise returns FALSE.
- *----------------------------------------- ----------*/
-
- BOOL bResult = TRUE; /* assume innocent */
- long lxTemp, lyTemp; /* hold calculations here */
- float flNewModsum; /* local modifier value */
- static float flModsum = (float)0.0;
-
- if (nCurrentLevel <= MAXIMUM_DEPTH) {
- flNewModsum = flModsum; /* Save the current value */
- switch (RootOrientation) {
- case NORTH:
- lxTemp = (long)xTopAdjustment +
- (long)(pThisNode->flPrelim + flModsum);
- lyTemp = (long)yTopAdjustment +
- (long)(nCurrentLevel * LEVEL_SEPARATION);
- break;
- case SOUTH:
- lxTemp = (long)xTopAdjustment +
- (long)(pThisNode->flPrelim + flModsum);
- lyTemp = (long)yTopAdjustment -
- (long)(nCurrentLevel * LEVEL_SEPARATION);
- break;
- case EAST:
- lxTemp = (long)xTopAdjustment +
-
- (long)(nCurrentLevel * LEVEL_SEPARATION);
- lyTemp = (long)yTopAdjustment -
- (long)(pThisNode->flPrelim + flModsum);
- break;
- case WEST:
- lxTemp = (long)xTopAdjustment -
- (long)(nCurrentLevel * LEVEL_SEPARATION);
- lyTemp = (long)yTopAdjustment -
- (long)(pThisNode->flPrelim + flModsum);
- break;
- }
-
- if (CheckExtentsRange(lxTemp, lyTemp)) {
- /* The values are within the allowable range */
-
- pThisNode->xCoordinate = (COORD)lxTemp;
- pThisNode->yCoordinate = (COORD)lyTemp;
-
- if (HasChild(pThisNode)) {
- /* Apply the flModifier value for this */
- /* node to all its offspring. */
- flModsum = flNewModsum =
- flNewModsum + pThisNode->flModifier;
- bResult = TreeSecondWalk(
- FirstChild(pThisNode),nCurrentLevel + 1);
- flNewModsum = flNewModsum -
- pThisNode->flModifier;
- }
-
- if ((HasRightSibling(pThisNode)) && (bResult)) {
- flModsum = flNewModsum;
- bResult = TreeSecondWalk(
- RightSibling(pThisNode), nCurrentLevel);
- }
- }
- else bResult = FALSE; /* outside of extents */
- }
- return (bResult);
- }
-
- static BOOL TreePosition(PNODE pApexNode)
- {
- /*------------------------------------------------------
- * Determine the coordinates for each node in a tree.
- * Input: Pointer to the apex node of the tree
- * Assumption: The x & y coordinates of the apex node
- * are already correct, since the tree underneath it
- * will be positioned with respect to those coordinates.
- * Returns: TRUE if no errors, otherwise returns FALSE.
- *----------------------------------------------------*/
-
- if (pApexNode) {
- /* Initialize list of previous node at each level */
- InitPrevNodeAtLevel();
-
- /* Generate the properly-placed tree nodes. */
- /* TreeFirstWalk: a post-order walk */
- /* TreeSecondWalk: a pre-order walk */
- if (TreeFirstWalk (pApexNode, FIRST_TIME)) {
-
- /* Determine how to adjust the nodes with */
- /* respect to the location of the apex of the */
- /* tree being positioned. */
- switch (RootOrientation) {
- case NORTH:
- case SOUTH:
- /* Create the adjustment from x-coord */
- xTopAdjustment = pApexNode->xCoordinate -
- (COORD)(pApexNode->flPrelim);
- yTopAdjustment = pApexNode->yCoordinate;
- break;
- case EAST :
- case WEST :
- /* Create the adjustment from y-coord */
- xTopAdjustment = pApexNode->xCoordinate;
- yTopAdjustment = pApexNode->yCoordinate +
- (COORD)(pApexNode->flPrelim);
- break;
- }
- return (TreeSecondWalk(pApexNode, FIRST_TIME));
- }
- else return (FALSE); /* Couldn't get an element */
- }
- else return (TRUE); /* Easy: null pointer was passed */
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Searching With Skip Lists
-
-
- Ken Grogan
-
-
- This article is not available in electronic form.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Removing Duplicate Files Across Disk Drives
-
-
- Jerzy Tomasik
-
-
- Jerzy Tomasik develops scientific application software for Dynamic Solutions
- Corp. His prior experience includes designing scientific experiments and
- developing data analysis tools in C, Oracle, and RS/1 under MS-DOS and VMS
- operating systems. Mr. Tomasik started programming in FORTRAN as a student in
- Krakow, Poland. You may contact him at 205 S. Redwood Ave. #29, Brea, CA
- 92621.
-
-
- All disk drives, regardless of their size, have one thing in common. Sooner
- than you think, they will run out of storage space. Sometimes all the files on
- your hard disk are really necessary, but more often you'll find a lot of files
- you could remove. Some excellent disk maintenance utilities are available, but
- they all lack one function. Few things are more frustrating than finding
- multiple copies of the same file scattered among different subdirectories.
- There are .EXE files that you have compiled, linked, and copied to your
- utilities directory, leaving an extra copy in the development directory. There
- are INSTALL and README files left from software installation that you no
- longer need. TURBO C Professional Development package creates UNPACK.EXE and
- README.COM in three different directories.
- For developers, multiple copies of source files scattered around the disk and
- different projects can be a real nightmare. Maybe you would like to put them
- in a library, but how do you find them all? The problems for a single-user
- disk drive pale in comparison to what you would face if you were responsible
- for a network drive. Invariably, every user will have a personal copy of
- something that could easily be shared. And don't forget that the smallest file
- size is the cluster size -- on my disk a one-byte file occupies 4,096 bytes.
- Multiple copies of a very small file add up quickly.
- Since none of the popular disk maintenance utilities are able to weed out the
- duplicates, I started thinking what I would need to make this task easy. I
- believe in simple, reusable tools, and the power of piping, redirection and
- batch files, but I could not come up with a scheme that would generate output
- appropriate for the task. The MS-DOS DIR command does not even scan
- subdirectories. CHKDSK/V scans subdirectories, but it does not provide any
- file information beyond pathname and name. Third party and shareware directory
- utilities are usually a great improvement over DIR, but they all produce
- output that requires extensive processing. Clearly, I needed a custom program.
-
-
- Design
-
-
- The general flow of the program is simple:
- read all files on the disk, recursing for subdirectories
- find all duplicate names
- print the duplicates, sorted by date/time and directory name.
- MS-DOS supports hierarchical directory structure, where any directory (except
- the root) has only one parent. Any directory can have zero or more children.
- Both files and subdirectories of any directory are stored in a DOS directory
- structure. (Advanced MS-DOS Programming by Ray Duncan describes DOS internals
- in detail.)
- DOS provides interrupt 21H, functions 4EH and 4FH, for locating directory
- entries. Function 4EH finds the first file, and 4FH finds subsequent files
- matching a wildcard pattern. Your program must communicate with these
- functions through a Data Transfer Area (DTA), which can be set by interrupt
- 21H, function 1AH.
- Microsoft C provides library functions _dos_findfirst and _dos_findnext to
- shelter the programmer from calling assembly routines. (Turbo C calls these
- functions findfirst and findnext.) An additional advantage of calling these
- functions is that _dos_findfirst implicitly sets up the DTA. Subsequent calls
- to _dos_findnext search for files according to what is stored in the DTA.
- After I discuss algorithms for scanning the disk, the role of the DTA will
- become clearer.
- There are two ways to process subdirectories while scanning the disk. When
- reading directory entries you may find files or subdirectories. When you find
- a subdirectory you have two options. Option one requires that you immediately
- descend to that subdirectory and process it, returning to the parent upon
- completion. The program must be able to handle this recursively. An
- alternative way saves new subdirectories, never interrupting processing of the
- current directory. The directories are saved in a structure that contains a
- flag indicating processed directories. Whenever the program completes
- processing of a directory, it marks that directory as processed and searches
- the directory list for another unprocessed directory.
- The first method requires that you save the current DTA, call the
- _dos_findfirst with the new path, process the new directory, and restore the
- DTA when you are done. After you restore the DTA, instead of calling
- _dos_findfirst, continue calling _dos_findnext. Essentially you pick up where
- you left off. The second method relieves you from saving and restoring the
- DTA. This is not difficult with the help of Microsoft or Turbo C libraries,
- but I find it to be too close to the operating system. This method will
- process all directories in the order of nesting, starting with root. It will
- process all first-level sub-directories, then second-level subdirectories,
- continuing until it reaches the deepest level.
- If there is any performance difference between the two methods, it is minimal.
- On my 386/25 machine with an 80Mb drive, the program scans the entire disk
- faster than the Norton Utilities FileFind.
- After choosing the directory processing algorithm, I had to design data
- structure(s), not a trivial task under Intel 80x86 segmented architecture.
- Storing full pathnames with each file (DirEntry structure) is out of the
- question due to size limitations. It is also unnecessary. Each directory entry
- can maintain a pointer to a record in a directory list (DirList structure),
- which allows storage of only a single copy of each pathname. (Note: You could
- further reduce the required storage space by storing only directory name and a
- pointer to the parent, instead of the entire path. With deeply nested
- subdirectories and long pathnames, the gain may be significant.)
- Even without the pathnames, the DirEntry structure occupies 28 bytes under the
- compact memory model. My 80Mb disk drive currently holds about 5,000 files,
- which translates to over 140Kb of RAM, since malloc allocates 28 bytes for
- each structure plus a block header for heap management.
- Notice that the DirEntry will not be stored as a contiguous block in the
- memory, since DirList entries will be interspersed throughout the same memory
- area. malloc allocates space for DirList or DirEntry, and you have no control
- of the allocation order. Whatever is found on the disk gets a chunk of memory.
- Although there are many ways to find multiple records, I chose to sort all the
- records and find identical adjacent records. The library version of qsort
- requires a contiguous array as input. Since the DirEntry records are not
- contiguous, you would have to add pointers to previous and next record to each
- DirEntry, then write a custom qsort.
- Instead of using a doubly linked list for DirEntry and writing my own sort
- routine, I opted for an array of pointers (PtrList) to the DirEntry records.
- In relational terminology, you have two tables, DirList and DirEntry with a
- "one to many" relationship. (For each record in the DirList, you have zero or
- more records in DirEntry, although you actually use the relation in the
- opposite direction, i.e., DirEntry to DirList.) The PtrList is an index for
- the DirEntry table. PtrList can be sorted using the qsort function. All you
- need is a comparison function.
- The PtrList is an array limited to 15,000 files, although you will run out of
- DOS base memory before reaching that limit.
-
-
- Language
-
-
- I currently use Turbo C at work and Microsoft C at home. Since both are very
- good compilers, I don't have a strong preference. Unfortunately, while both
- compilers are very close to ANSI C, they differ significantly in the MS-DOS
- specific area. The functionality is similar, but function names, argument
- order, structure definitions, and compiler defines are different. Porting a
- program from one to another could prove time consuming. I attempted using a C
- preprocessor to hide the differences.
- This exercise proved to be worthwhile. I managed to hide all the differences
- in a header file (Listing 1). The executable code (Listing 2) contains no
- compiler-specific references. Several solutions I've found are worth passing
- on, since they may save a lot of your time.
- I have seen many listings that laboriously define their own macros with names
- like MICROSOFT_C or TURBO_C. Both compilers have predefined values, which are
- there for you to use. Microsoft defines _MSC_VER and TURBO defines __TURBOC__.
- By using these values you don't have to modify the code or pass a compile-time
- define. This can be extremely valuable if you plan to distribute source code
- supporting more than one compiler.
- Another useful trick is using macros to substitute not only function names but
- also argument order, as done for findfirst and _dos_findfirst. I chose to use
- Turbo nomenclature in the code, but the choice is arbitrary. You may define
- your own names and provide defines for all the compilers you want to support.
- Notice also how structures ffblk and find_t are translated along with their
- members. These defines are a little more dangerous, since they may start
- colliding with your variable names (in this case size and name are likely
- candidates). If you decide to use this technique for larger programs, I
- recommend using a systematic approach to name substitutions. Using this
- systematic approach guarantees that the names you want to translate are
- unique.
- To make your code even more maintainable, you could put compiler-specific
- defines in their own headers, and include them in your program headers as:
- #if defined( _MSC_VER )
- #include <local\msc.h>
- #elif defined( __TURBOC__ )
- #include <local\turboc.h>
- #endif
-
-
- Conclusion
-
-
- The first programming language I learned was FORTRAN, followed by BASIC. When
- I started learning C, I had difficulties with the pointers. However, my
- difficulties were in seeing what the pointers were used for, even after I
- learned how to use them. Most of the pointer examples in popular C books show
- string functions, and BASIC has great string handling without the need for
- pointers, so I admit I had my doubts. Try to imagine implementing this program
- without pointers and structures. I'm sure it can be done, but readability and
- performance will suffer.
- This program can serve as a skeleton for other custom programs, as it is
- essentially a barebone directory scanning program. You could enhance the
- program by processing wildcards other than *.*, offering multiple disk drive
- support, and/or spawning a user program (e.g., for file comparison). As a more
- advanced exercise, you could add virtual memory support.
-
-
- Listing 1 (no_dup.h)
- /*
- Program relies on definitions of _MSC_VER or __TURBOC______LINEEND____
- to account for compiler differences between Turbo C and
- Microsoft C. These values are predefined.
- */
-
- #if defined( _MSC_VER )
-
- #include <malloc.h>
- #include <direct.h>
-
- /* Turbo */ /* Microsoft */
- /* translate structure name and members */
- #define ffblk find_t
- #define ff_reserved reserved
- #define ff_attrib attrib
- #define ff_ftime wr_time
- #define ff_fdate wr_date
- #define ff_fsize size
- #define ff_name name
- /* translate attribute mask defines */
- #define FA_RDONLY _A_RDONLY
- #define FA_HIDDEN _A_HIDDEN
- #define FA_SYSTEM _A_SYSTEM
- #define FA_LABEL _A_VOLID
- #define FA_DIREC _A_SUBDIR
- #define FA_ARCH _A_ARCH
- /*
- use macros to substitute functions and
- swap argument order
- */
- #define findfirst( a , b , c ) _dos_findfirst((a),(c),(b))
- #define findnext( a ) _dos_findnext((a))
-
- /* end of _MSC_VER */
- #elif defined( __TURBOC__ )
-
- #include <dir.h>
- #include <alloc.h>
-
- #endif /* __TURBOC__ */
-
- #include <dos.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <time.h>
-
- /* defines */
- #define VERSION "NO_DUP Ver 1.0, Jerzy Tomasik (c) 1990"
- #define ALL_FILES 0xFFFF /* flag for _dos_find??? */
- #define MAX_LINE 80
- #define MAX_PATH 80
- #define MAX_FLEN 13
- #define MAX_FILES 15000
- #define SEPARATOR "************\n"
- /* data definitions and declarations */
-
-
- typedef enum { False, True } Flag;
-
- /*
- DirEntry structure holds data for all files
- */
- typedef struct DirEntry
- {
- char *path;
- char name[13];
- char attrib;
- unsigned time;
- unsigned date;
- long size;
- Flag dir_processed;
- } DirEntry;
- /*
- DirList structure holds the listing of all
- subdirectories on a disk
- */
- typedef struct DirList
- {
- char pathname[MAX_PATH];
- Flag dir_processed;
- struct DirList *prev;
- struct DirList *next;
- } DirList;
- typedef struct
- {
- Flag dir;
- Flag file;
- unsigned int file_count;
- unsigned long total_file_size;
- } GlobalOpt;
- typedef char *PtrList;
-
- /* function prototypes */
- DirEntry *get_direntry ( char * );
- DirList *make_path ( char *, char *, DirList * );
- DirList *get_path ( DirList * );
- int name_comp ( const void *, const void * );
- char *datestr ( unsigned d, char *buf );
- char *timestr ( unsigned t, char *buf );
- void fprint_direntry ( FILE *, DirEntry * );
-
-
- Listing 2 (no_dup.c)
- /*
- Find multiple copies of the same file on a single disk
- drive. Compiled under Microsoft C 6.0 using:
- cl /Fs /W4 /Ox /AC no_dup.c
- Compiles without modifications undder TURBO C integrated
- environment.
- In both cases requires compact memory model libraries.
- */
-
- #include "no_dup.h"
-
- Flag first_call = True;
-
-
- GlobalOpt globalopt = { False, False, 0, 0L };
-
- int main( int argc, char **argv )
- {
- DirEntry *current_file, *next_file;
- DirList *path, *root, *new_path;
- char current_path[MAX_PATH], file_spec[MAX_FLEN];
- PtrList *ptrbase, *ptrlist;
- Flag duplicate = False;
-
- for( ; argc > 1; argc-- )
- {
- if(argv[argc-1][1] == 'f' argv[argc-1][1] == 'F')
- globalopt.file = True;
- else if( argv[argc-1][1] == 'd'
- argv[argc-1] [1] == 'D' )
- globalopt.dir = True;
- else
- fprintf( stderr,
- "Invalid command line switch: %s\n",
- argv[argc-l] );
- }
-
- strcpy( file_spec, "*.*" );
- current_file = NULL;
- (void) getcwd( current_path, MAX_PATH );
- fprintf( stderr, "%s\n", VERSION );
- printf( "Processing %s\n", current_path );
- path = make_path( current_path, "", NULL );
- new_path = root = path;
- if( (ptrbase = malloc( sizeof( PtrList ) * MAX_FILES ))
- == NULL )
- {
- fprintf( stderr, "Insufficient near memory.\n" );
- exit( EXIT_FAILURE );
- }
- ptrlist = ptrbase;
-
- while( (path = get_path( root ) ) != NULL )
- {
- strcpy( current_path, path->pathname );
- strcat( current_path, file_spec );
- first_call = True;
-
- while( (current_file = get_direntry(current_path))
- ! = NULL )
- {
- current_file->path = path->pathname;
- if( current_file->attrib & FA_DIREC )
- {
- new_path = make_path( path->pathname,
- current_file->name, new_path );
- }
- else
- {
- ++globalopt.file_count;
- globalopt.total_file_size
- += current_file->size;
-
- if( globalopt.file_count == MAX_FILES )
- {
- fprintf( stderr,
- "Too many files\n"
- "Program is limited to %u files.\n",
- MAX_FILES );
- exit( EXIT_FAILURE );
- }
- *ptrlist = ( char * ) current_file;
- ++ptrlist;
- }
- }
- path->dir_processed = True;
- }
- *ptrlist = NULL;
- printf( "Processed %d files occupying %lu bytes.\n",
- globalopt.file_count,
- globalopt.total_file_size );
-
- qsort( (void *) ptrbase,
- (size_t) globalopt.file_count,
- (size_t) sizeof( PtrList ), name_comp );
-
- if( globalopt.file ) /* optionally list files */
- {
- printf( SEPARATOR );
- printf( "Listing of all files below %s\n",root);
- for( ptrlist = ptrbase; *ptrlist; ptrlist++ )
- fprint_direntry( stdout,
- (DirEntry *) *ptrlist );
- }
-
- printf( SEPARATOR );
- printf( "Duplicate files below %s\n", root );
- for( ptrlist = ptrbase, duplicate = False;
- *ptrlist; ptrlist++ )
- {
- current_file = ( DirEntry * ) *ptrlist;
- next_file = ( DirEntry * ) *(ptrlist+1);
- if( !strcmp(current_file->name,next_file->name))
- {
- duplicate = True;
- fprint_direntry( stdout, current_file );
- {
- else if( duplicate == True )
- {
- /* print the last file in a group */
- /* otherwise we'll miss it */
- current_file = ( DirEntry * ) *ptrlist;
- fprint_direntry( stdout, current_file );
- printf( SEPARATOR );
- duplicate = False;
- }
- }
-
- return EXIT_SUCCESS;
- }
-
- /*
-
- get file info from DOS directory and allocate memory
- */
- DirEntry *get_direntry( char *path )
- {
- static struct ffblk file_info;
- DirEntry *new_entry;
-
- if( first_call == True )
- {
- if( findfirst( path, &file_info, FA_DIREC ) )
- return NULL;
- first_call = False;
- }
- else
- {
- if( findnext( &file_info ) )
- {
- return NULL;
- }
- }
- if( (new_entry = malloc(sizeof(DirEntry))) == NULL )
- {
- fprintf( stderr, "Insufficient far memory.\n" );
- exit( EXIT_FAILURE );
- }
-
- strcpy( new_entry->name, file_info.ff_name );
- new_entry->attrib = file_info.ff_attrib;
- if( new_entry->attrib & FA_DIREC )
- new_entry->dir_processed = False;
- new_entry->time = file_info.ff_ftime;
- new_entry->date = file_info.ff_fdate;
- new_entry->size = file_info.ff_fsize;
-
- return new_entry;
- }
-
- /*
- build a directory path by appending subdir to basedir
- */
- DirList *make_path( char *basedir, char *subdir,
- DirList *prev )
- {
- DirList *new_dir;
-
- if( (new_dir = malloc( sizeof( DirList ) ) ) == NULL )
- {
- fprintf( stderr, "Insufficient near memory.\n" );
- exit( EXIT_FAILURE );
- }
- strcpy( new_dir->pathname, basedir );
- strcat( new_dir->pathname, subdir );
- if( *(new_dir->pathname + strlen(new_dir->pathname) - 1)
- != '\\' )
- strcat( new_dir->pathname, "\\" );
- if( strcmp( subdir, "." ) && strcmp( subdir, ".." ) )
- {
- /* only "real" directories meet the condition */
- new_dir->dir_processed = False;
-
- if( globalopt.dir )
- printf( "Directory:%s\n", new_dir->pathname );
- }
- else
- new_dir->dir_processed = True;
- if( prev )
- {
- new_dir->prev = prev;
- new_dir->next = NULL;
- prev->next = new_dir;
- }
- else
- {
- new_dir->prev = NULL;
- new_dir->next = NULL;
- }
- return new_dir;
- }
-
- /*
- find an unprocessed directory
- */
- DirList *get_path( DirList *root )
- {
- DirList *path;
-
- path = root;
- while( path->next && (path->dir_processed == True) )
- {
- path = path->next;
- }
- if( path->dir_processed == True)
- return NULL;
- else
- return path;
-
- }
-
- /*
- DirEntry comparison function
- */
- int name_comp( const void *p1, const void *p2 )
- {
- PtrList *ptr1, *ptr2;
- DirEntry *f1, *f2;
- int status;
-
- ptr1 = (PtrList *) p1;
- ptr2 = (PtrList *) p2;
- f1 = (DirEntry *) *ptr1;
- f2 = (DirEntry *) *ptr2;
-
- if( status = strcmp( f1->name, f2->name ))
- return( status );
- else if( status = f1->date - f2->date )
- return( status );
- else if( status = f1->time - f2->time )
- return( status );
- else
-
- return( strcmp( f1->path, f2->path ) );
- }
-
- /*
- print file information to the ouptut stream
- */
- void fprint_direntry( FILE *fout, DirEntry *current_file )
- {
- char date_buf[10], time_buf[10];
-
- fprintf( fout, "%-14s %6ld %c%c %s %s %s\n",
- current_file->name,
- current_file->size,
- (current_file->attrib & FA_RDONLY ) ? 'R' : '.',
- (current_file->attrib & FA_ARCH ) ? 'A' : '.',
- datestr( current_file->date, date_buf ),
- timestr( current_file->time, time_buf ),
- current_file->path );
-
- }
-
- /*
- The following functions are copied from QC 2.0 online
- help. These functions convert wr_time and wr_date into
- strings.
- */
- char *timestr( unsigned t, char *buf )
- {
- int h = (t >> 11) & 0x1f, m = (t >> 5) & 0x3f;
- sprintf( buf, "%2.2d:%02.2d", h, m );
- return buf;
- }
-
- char *datestr( unsigned d, char *buf )
- {
- sprintf( buf, "%2.2d/%02.2d/%02.2d",
- (d >> 5) & 0x0f, d & 0x1f, (d >> 9) + 80 );
- return buf;
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Reviving The UNIX sbrk Function
-
-
- David A. Schmitt
-
-
- Dave Schmitt was a founder and president of Lattice, the well-known C compiler
- company, from 1983 until 1990. Prior to that, he worked at Bell Telephone
- Laboratories where he was involved in the design of fault-tolerant operating
- systems, including a nonstop version of UNIX. He is currently a free-lance
- author and consultant. You may contact him at Pivot, (708) 469-2235.
-
-
- Early versions of UNIX offered C programmers a simple memory allocator using a
- linear heap and two functions named sbrk and brk. (Later systems renamed the
- latter rbrk.) This heap management system is fast and efficient because,
- unlike more sophisticated random heap systems, it requires no overhead to keep
- track of allocated and free blocks.
- A random heap manager usually adds a pointer and an integer to each block. The
- integer contains the block size, and the pointer is used to chain the block
- into a linked list. I've seen some applications in which nearly 20 percent of
- the memory was lost to this overhead information. This typically occurs when
- you need to allocate a lot of very small blocks, such as when building a
- compiler symbol table.
- Nonetheless, a linear heap is not as generally useful as a random heap, so the
- ANSI committee did not include sbrk and rbrk in the standard C library. They
- perpetuated only the UNIX random heap functions named malloc, free, calloc,
- and realloc. As a result, compiler vendors are gradually dropping sbrk and
- rbrk from their libraries.
- Despite its omission from the ANSI standard, the linear heap has advantages in
- some applications. This article shows how you can implement sbrk and rbrk in
- an environment that offers only the ANSI random heap functions.
-
-
- Linear Heap Management
-
-
- A linear heap is a contiguous memory area divided into allocated and
- unallocated portions as shown in Figure 1. A break pointer contains the
- address of the unallocated portion. It initially points to the beginning
- (i.e., the low address) of the heap. You allocate memory by moving the pointer
- upwards (i.e., by increasing its address), and you free memory by moving the
- pointer downwards. The sbrk function handles both of these operations, as
- shown in Listing 1.
- The first two calls to sbrk allocate a 100-byte block and a 200-byte block,
- assigning the block addresses to pointers p and q, respectively. The third and
- fourth calls free both blocks by telling sbrk to move the break pointer first
- 200 bytes and then 100 bytes in the negative direction. You could combine
- these into a single call moving the break pointer down 300 bytes. Also, you
- can call rbrk to reset the break pointer to its initial position, thereby
- freeing all allocated blocks.
-
-
- Linear Heap Applications
-
-
- The example in Listing 1 shows the primary disadvantage of the linear heap:
- You must always free blocks in the reverse order that they were allocated. If
- you no longer need the 100-byte block, you cannot release it until you are
- done with all the blocks allocated after it. Indeed, the linear heap manager
- knows nothing about blocks; it merely moves a pointer up and down.
- Despite this disadvantage, the linear heap is well-suited to some
- applications. Suppose you're building a symbol table for a compiler. Each
- symbol is represented by a structure having the format shown in Listing 2.
- When the compiler encounters a new symbol, it builds a SYMBOL structure on the
- stack and then calls a function named savesym to save this information in the
- heap. When the function returns, the compiler links the new SYMBOL structure
- into the appropriate place in an alphabetical list. These structures remain
- allocated throughout the compilation and are all released at the same time
- when the compiler is finished. This is clearly a case where the overhead of a
- random heap is unnecessary.
- To show the difference between the two heap methods, the savesym function can
- be written to use either sbrk or malloc, as shown in Listing 3.
- This function uses sbrk if the symbol LHEAP is defined; otherwise, it uses
- malloc. Note that savesym returns NULL if no space can be allocated. The
- strange if statement after the sbrk call is necessary because sbrk does not
- return NULL when it fails. Instead, it conforms to the original UNIX
- definition by returning the value -1 cast to a pointer.
- The little test program in Listing 4 shows how many symbols we can save under
- various conditions. Figure 2 shows the results of running this test program
- with the linear and random heap, using the Lattice compiler and various symbol
- sizes. The left column indicates the size of the symbol. A size of 8 produces
- a 19-byte SYMBOL structure, while a size of 255 produces a 266-byte structure.
- The percentage column is computed by taking the difference between the sbrk
- and malloc results, divided by the malloc results.
- This test indicates that, given a typical implementation, sbrk is about 25
- percent more space efficient than malloc when allocating small blocks. As the
- block size increases, this advantage gradually disappears.
- Since many programming languages use small symbols of 32 bytes or less, the
- linear heap method can make a noticable difference in compiler memory
- consumption. In general, you should consider using sbrk if your application
- allocates a lot of small blocks that it keeps until termination or until the
- beginning of another processing phase. In such an application, you can also
- get a performance improvement with a linear heap because it allows you to
- release all memory with a single call to rbrk rather than with multiple calls
- to free.
-
-
- Implementing A Linear Heap
-
-
- If your favorite C compiler doesn't support sbrk and rbrk, you can easily
- implement them on top of malloc and free. The code is shown in Listing 5.
- The operation of sbrk is straightforward. On the first call, it uses malloc to
- get a block whose size is the larger of the requested size and the global
- parameter _LHINCR. Local variables base and size are used to save the base
- address and size of this block, respectively. The variable xbrk is the offset
- of the break location within the block. It is initially set to zero. In other
- words, the current break address is &base[xbrk]. In keeping with the UNIX
- definition of sbrk, a new block is cleared -- memset is called if n is greater
- than 0.
- The rbrk function is even simpler. If a linear heap has been created, it frees
- the block and resets all the local variables. Otherwise, it does nothing.
- Notice that _LHINCR normally causes the linear heap to contain 8 kilobytes.
- You can change this to a different value before the first call to sbrk or
- after calling rbrk. You might wonder why this approach is used rather than
- having sbrk call realloc to expand the linear heap when it cannot honor a
- request. The problem is that realloc may move the heap, thereby invalidating
- the pointers returned by all previous sbrk calls.
-
-
- Final Thoughts
-
-
- The latest compiler from Microsoft does not provide sbrk and rbrk, so you can
- use the functions from Listing 5 with no problems.
- Borland provides a verison of sbrk that performs about the same as Lattice's.
- However, Borland's C compilers do not have an rbrk function. This is not a big
- problem if you maintain a total of all the space you have allocated via sbrk.
- You can then simulate rbrk by calling sbrk with the negative of this total
- value.
- Watcom and Zortech also provide sbrk but not rbrk. However, you may not want
- to use their linear heaps because of the implementation's overhead. When I ran
- the Listing 4 test program with these two compilers, I discovered that the
- sbrk version of savesym stored fewer symbols than the malloc version.
- This discovery led me to examine the source code that Zortech supplies in
- their Developer's Edition. It turns out that each time you call their sbrk
- function, they invoke DOS to get more memory. Since DOS allocates in multiples
- of 16-byte paragraphs, each sbrk request is rounded up to the next multiple of
- 16. This is considerably more overhead than Zortech's malloc, which rounds
- each request up to the next multiple of four.
- Zortech's implementation of sbrk also violates the spirit of the original UNIX
- version, where the requested size was not rounded up and each allocated block
- was immediately above its predecessor. To be fair, however, Zortech says this
- about sbrk in their manual: "Applications should avoid using it." If that's
- the case, I wonder why they bothered to include it in their library and
- documentation.
- The Watcom version of sbrk behaves so much like Zortech's that I suspect it
- uses a similar strategy, although I did not have access to their library
- source. It's clear that if you can benefit from the linear heap approach, you
- should use the routines from Listing 5 even in the Watcom and Zortech
- environments, which supposedly support this feature. To be sure of linking in
- the correct version, you might want to put a leading underscore in front of
- your sbrk and rbrk routines, or give them completely different names.
- Figure 1 Linear Heap
- Figure 2 Test Results
- SIZE sbrk malloc RATIO
-
-
- 8 2964 2325 27.5%
- 12 2448 1993 22.8%
- 16 2085 1743 19.6%
- 20 1816 1550 17.2%
- 24 1609 1395 15.3%
- 28 1444 1268 13.8%
- 32 1309 1162 12.7%
- 128 405 387 4.6%
- 255 211 205 2.9%
-
- Listing 1 Using srbk
- void *sbrk(int n); // prototype for sbrk
-
- char *p,*q; // two pointers for allocated
- blocks
- p = (char *)sbrk(100); // allocate a 100-byte block
- q = (char *)sbrk(200); // allocate a 200-byte block
-
- sbrk(-200); // free the 200-byte block
- sbrk(-100); // free the 100-byte block
-
-
- Listing 2 The SYMBOL Structure
- typedef struct _SYMBOL
- {
- struct _SYMBOL *next;// linkage to next symbol
- unsigned long type; // type flags
- unsigned long value; // value
- char symbol[1]; // symbol string (variable size)
- } SYMBOL;
-
-
- Listing 3 The savesym Function
- SYMBOL *savesym(SYMBOL *s)
- {
- int size;
- SYMBOL *p;
-
- size = sizeof (SYMBOL) + strlen(s->symbol);
- #ifdef LHEAP
- p = sbrk(size);
- if(p == (void *)(-1)) return(NULL);
- #else
- p = malloc(size);
- #endif
- if(p) memcpy(p,s,size);
- return(p);
- }
-
-
- Listing 4 Test Program
- void main (void)
- {
- char b[32]; // Buffer for user input
- int size; // Symbol size
- int i; // Loop counter
- union // Space for test symbol
- {
- SYMBOL x;
-
- char y[sizeof(SYMBOL)+255];
- } sym;
-
- memset(&sym,0,sizeof(sym)); // Clear symbol area
- printf("Symbol size? "); // Get symbol size from user
- gets(b);
- size = atoi(b);
- if(size < 0) size = 0; // Make sure size is in bounds
- if(size > 255) size = 255;
- memset(sym.x.symbol,'X',size); // Set symbol to X's
-
- for(i = 0; savesym(&sym.x) != NULL; i++); // Allocate all memory
- printf("\n%d symbols allocated\n",i); // Print number of symbols
- }
-
-
- Listing 5 The srbk and rbrk Functions
- #include <stdlib.h>
- /**
- *
- * name sbrk -- Adjust linear heap break pointer
- * rbrk -- Reset linear heap
- *
- * synopsis b = sbrk(n);
- * rbrk();
- * void *b; block address
- * int n; number of bytes
- *
- * description This is an implementation of sbrk/rbrk
- * based upon the ANSI memory allocation
- * routines. Note that it uses a public
- * item named _LHINCR to define the
- * default size of the linear heap. This
- * size is used to allocate space via
- * malloc on the first call to sbrk
- * unless the requested size is larger.
- *
- **/
- #define SBRK_ERR (void *)(-1)
- /**
- *
- * Linear heap parameters
- *
- **/
-
- unsigned _LHINCR = 8192; // allocation size of
- linear heap
-
- static char *base = 0; // base of linear heap
- static unsigned size; // size of linear heap
- static unsigned xbrk = 0; // current break index
- /**
- *
- * Allocate space from the linear heap.
- *
- **/
- void *sbrk(int n)
- {
- unsigned x;
-
-
- if(base == 0)
- {
- if(n <= 0) return(SBRK_ERR);
- size = (n > _LHINCR) ? n : _LHINCR;
- base = malloc(size);
- if(base == 0)
- {
- size = 0;
- return(SBRK_ERR);
- }
- xbrk = 0;
- }
- if(n < 0)
- {
- if((xbrk + n) < 0) return(SBRK_ERR);
- }
- else
- {
- if((xbrk + n) > size) return(SBRK_ERR);
- }
- x = xbrk;
- xbrk += n;
- if(n > 0) memset(&base[x],0,n);
- return(&base[x]);
- }
- /**
- *
- * Reset the linear heap
- *
- **/
- void rbrk(void)
- {
- if(base != 0)
- {
- free(base);
- base = 0;
- size = 0;
- xbrk = 0;
- }
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Automated Software Testing
-
-
- Robert McLaughlin
-
-
- Robert McLaughlin is Principal Engineer for Check*mate. He has been a C
- programmer for 12 years. Bob has written several articles and is an author of
- "Fix Your Own PC," MIS Press 1990. His interests include collecting
- mathematical puzzles and ocean watching. Mr. McLaughlin can be reached at PRA,
- 1953 Gallows Rd. Suite 350, Vienna VA 22182. (703) 883-2522.
-
-
- Software development is generally characterized by a cycle as shown in Figure
- 1. The first step is developing a specification. The second step is validating
- this specification. The third step has two parts that occur at the same time:
- coding based on the specification, and developing a test plan. These two parts
- are generally done by independent groups. The fourth step is validating the
- coding through debugging. The fifth step is validating that the code meets the
- specification. The sixth step is customer acceptance, verifying that the
- delivered product works and meets the customer's interpretation of the
- specification.
- In his paper, "Applying Automation to the Test Process" [1], David Godfrey
- discusses how changes increase in cost exponentially as they are made later in
- the development cycle. The more work put into the specification, the less the
- software will cost, since fewer omissions will have to be corrected. The
- sooner bugs are caught, the less it will cost to fix them. Also, the fewer
- bugs in the delivered software, the greater the customers' confidence that the
- code works, speeding up customer acceptance.
- The process of specification development is human-to-human interaction. No
- program can ever make this process easier. All you can hope for is that the
- specification can be made clearer. Prototyping helps for example, or using an
- abstract language such as ASN.1 [2]. Until users understand the specification
- process enough to ensure that the specification is correct, specification
- development will be the area in which the greatest number of bugs are
- introduced.
- The benefits of automating the testing process are two-fold. It helps ensure
- that the code delivered meets the specification and is free of most bugs.
- Automated testing is not a panacea. It cannot ensure that your code is 100
- percent bug-free, nor can it solve problems caused by a poor specification.
- Automated testing is a program testing a program. If there is a bug in your
- testing program, it will cause good code to be declared bad.
-
-
- Automated Testing
-
-
- One way to develop and automate a test plan is to use a product called
- Check*mate. Check*mate is a set of Microsoft C library routines that enable
- automated testing of serial telecommunications through two serial ports. The
- product is one of many automated testing packages on the market. Check*mate's
- niche is telecommunications.
- Other testers exist for different applications. I suggest that you examine
- them all before purchasing one. None of these packages are actually
- all-purpose. Because each product has its own market niche, one particular
- product may meet your needs far better than the others.
- Two other automated testing packages I will mention here are Autotester and
- the Atron Evaluator. Autotester, from Software Recording Corp., is a
- playback-capture tool for telecommunications. It captures keystrokes and
- responses to the program under test to allow playback for regression testing.
- The Atron Evaluator is a playback-capture tool used on PCs running OS/2 or
- MS-DOS. The Evaluator works with one PC controlling another PC using special
- hardware. The Evaluator can play back the details of a session, including
- mouse movements. Both packages allow you to edit captured keystroke scripts.
- Autotester also allows a certain amount of branching.
- Regression testing is the process of retesting. Say, for example, you change
- the code that controls the way your program handles queues. Ideally, each
- subroutine that calls the queuing features should be retested. This retesting
- process is called regression testing.
- This can quickly become tiresome. In fact, it is in regression testing that
- programmers take the greatest liberty with "Oh yeah, it works." So automating
- this process is very useful, since it ensures that changes have not introduced
- new bugs.
-
-
- Review Of Testing Methods
-
-
- A great deal of money is spent on testing, currently over half the budget of
- most large scale projects so a lot of thought has gone into how to improve
- testing.
- In spite of the efforts to improve testing, there is no cure-all. Just as PL/I
- was going to save us all from ourselves 14 years ago, each new idea in testing
- is promoted to save us from ourselves. Computers do only what we tell them to
- do. If our methodology is bad, then no tool will save us from ourselves. If
- the specification process is sloppy, no test procedure will help us deliver
- the code the customer needs.
- Testing should always be done from the standpoint of the customer. In a
- database application your concern may be in file structures. But in the
- customer's mind, bugs are screens that don't ask the right questions or
- reports that are hard to read. In a telecommunications application, you must
- demonstrate that the product conforms to some standard, such as X.25 [3]. You
- can do this by performing tests in accordance with some other standard, such
- as ISO-8882 [4].
-
-
- Testing All The Code
-
-
- The difficulty that many programmers have with testing is developing a test
- plan. They do not like the idea of someone looking over their shoulder.
- However, a good test plan should be based entirely on the specification and
- should be done by a group independent of the programmers. A test plan will not
- work if the test code is based on the code to be tested or written by those
- that have knowledge of the code that is to be tested. This means that a second
- group parallel to the programmers is required. This second group helps ensure
- that the code meets specification.
- A test plan must both exercise all the features of the program and all the
- segments of the code. This task is not as simple as it sounds. A typical
- feature-based test plan tests only 80 percent of the code. One reason is that
- some of the code is borrowed from other programs and never used in the current
- program. Also, a feature test does not test error conditions and other
- paranoid conditions that programmers write code for.
- The difficulty even with well-tested code is that conditions occur in the
- field that were not tested for. The only way to ensure that the code does not
- break down in an unexpected manner is to use a profiler, such as the UNIX
- utility PROF to ensure that every line is being exercised, that all calls to
- all routines are used. If this is not done then a program that passes a test
- plan may break down in the field because a subroutine was not tested under all
- conditions. This is especially true for routines that handle queuing or link
- lists.
- The development of a test plan is not an easy matter. Several decisions must
- be made. To what extent is the software to be tested? That is, how much
- testing needs to be done to get a feeling that the program works? How
- important is it that features meet the specification exactly? How much
- regression testing must be done to ensure that a bug fix does not introduce
- more bugs?
- With these questions answered, it is possible to devise a test plan. The test
- is generally based on the specification and is designed to allow no more than
- one bug in 1,000 lines of code. Some applications require that bugs occur no
- more frequently than one bug per 1,000,000 lines of code. The allowed bug rate
- and how fuzzily features can match the specification determine the type and
- extent of testing. In telecommunications, the code must have the exact
- features of the specification and generally not more than one bug in 10,000
- lines of code.
-
-
- Some Words On Check*mate
-
-
- Check*mate is a program within a program. It is composed of two parts. One
- part is an environment that handles I/O and does a certain amount of
- multitasking under MS-DOS. The second part is the user's script.
- The script is a Microsoft C program that is compiled and linked into an .EXE
- file using the standard Microsoft tools. For it to mesh with the environment
- part, it requires an include called cm.h. Also at link time the Check*mate
- libraries must be linked in.
- The use of C gives the tester great power, but this great power can drive
- people mad. For simple regression testing -- testing where keys need to be
- played back and exact responses compared -- Autotester is far better. In a
- situation where different responses can be returned depending on the state of
- the process under test, such as is the case in X.25, then using C makes sense.
- The example of automated testing (Figure 1) is devised more from the view of
- clarity than practical utility.
- I assume the program under test is running on a UNIX machine that can be
- dialed into, and that the program can be run over a modem. It does not matter
- for my purposes where the program under test lives. It does matter that I
- ensure my testing program sets up the serial port correctly, logs into the
- machine, starts the program to be tested running, tests the program, logs off,
- and resets the serial port as required. Since setting up and resetting the
- serial port are common to many tests, I have placed them in an include file.
- For the same reason, I have done the same with logging on and off the target
- machine.
- The customer specification is seen in the box Customer Request. It is short
- and to the point. It does not, however, discuss how the program is to end.
- In reply to the customer request, a Specification is written. It is shown in
- the Specification box. It says what the Customer Request says but in technical
- phrasing. Note that it, too, omits how the program is to end.
- Listing 1 is the program as coded according to the specification. It reads in
- a character. If the character passes islower, it is turned into an uppercase
- letter, using toupper. No matter what, the character is echoed. Since nothing
- ever specified how the program is to end, the program does not end.
- Based on the specification, the quality control department writes a test plan,
- which is shown in the Test Plan box. This may be written in English, as I have
- done here, or in TTCN or some other abstract language. In telecommunications,
- TTCN is the language of choice for test plans. Since the specification says
- nothing about how the program is to end, it does not mention testing if the
- program ends correctly.
- Listing 2 is the Check*mate-based testing program. The testing program is
- bigger and more complex than the program under test, which is often the case.
- This is tolerable because in many testing situations the code is too complex
- for any other means to be reliable. The test code assumes that in the include
- files setups.h and log.h are the routines to set-up the serial port and to log
- on and off the target machine.
- The test code uses three Check*mate routines, TX_chr, RX_chr, and CM_log.
- TX_chr places the character in a transmit queue, Check*mate sends that
- character when it is next up in the queue. RX_chr gets the next character from
- the receive queue. CM_log puts a formatted message in a log file. We will
- examine the log file to see how well the test did. Check*mate includes
- routines to sort through the receive queue for a string, to time response,
- etc.
-
- Listing 2 goes slightly beyond the test plan. It tests not only to see if a
- lowercase letter is turned into uppercase, but also if other letters are
- echoed back as they were sent -- something implied by the test plan. The
- testing program uses a BREAK to cause the program under test to end. The
- testing program logs a failure by noting that a character returned was not the
- one expected. A count of correctly returned characters is kept to obtain a
- sense of how badly the program failed.
- In the Sample Session (Figure 2), the Log box shows the log that the test
- program would create. The first five lines note that Check*mate was loaded
- under what MS-DOS, with how much free memory, and with how many serial ports.
- The last four lines are the log messages of the test. The first MSG line notes
- that the test has started. The second and third MSG lines note failures. The
- fourth MSG line notes the number of correct characters sent and received. The
- fifth MSG line notes the end of the test.
- The failures are of interest. The first failure is possibly line noise. The
- second failure is clearly a bug. See if you can find it. In general, the test
- should be run several times to ensure that no gremlin has caused the random
- number generator to produce only non-lowercase letters.
-
-
- Summary
-
-
- I have attempted to demonstrate the wonders and pitfalls of automated testing.
- Automated testing does not lessen the difficult job of program specification
- -- it will not help you when you are sloppy. Since automated testing brings in
- another body of people, quality control, the specification needs to be
- tighter.
- In the example, the failure to find out how the program was to end resulted in
- the writing of a program that could not be cleanly ended, and a testing
- program that would pass it. Clearly the customer did not intend the program to
- run forever. When the code is delivered, the customer will, to the surprise of
- all, reject it. All that automated testing did in this case is reduce the cost
- of testing the code according to the specification.
- Automated testing cannot test your specification process. However, the
- mistakes made in this phase will cost the most to fix if they are not caught
- until the customer acceptance phase.
- In this simple testing program, more that 1,000 tests of the program were
- done. Imagine sitting at a keyboard and typing in 1,000 random letters in less
- than five minutes. Imagine doing this several times in a row. Imagine finding
- a bug, and having to do it all over again.
- Automated testing will allow greater assurance at a reasonable cost that your
- code meets specification with minimum bugs. It does not help you assure that
- the specification meets the customer needs. This area is the next frontier in
- software engineering.
- References
- [1] David Godfrey, "Applying Automation to the Test Process," Proceedings of
- the 6th International Conference on Testing Computer Software, US Professional
- Development Institute, Silver Spring MD, 1989.
- [2] CCITT Blue Book. X.208 and X.209.
- [3] CCITT Blue Book. X.25
- [4] ISO Draft Standard 8882.
- CHE01 Check*mate Users Guide.
- Figure 1
- Customer Request
-
- A way to echo
- small letters as
- capitals.
-
- Specification
-
- We will write a C program that will read a
- character from a terminal device and echo
- back to the terminal device a capital when
- small letters are detected. Printable
- characters other than small letters will be echoed
- back unchanged.
-
- Test Plan
-
- Randomly generate characters. Pass if printable
- characters other than small letters are echoed back
- exactly as sent. Pass if small letters are echoed back
- as capitals. Fail if other occurs.
- Figure 2
- 12:00:00.00 SYS \BIN\CM.EXE
- 12:00:00.05 SYS 073190: Checkmate 1.1 loaded
- under DOS 3.3
- 12:00:00:10 SYS 640KB total memory, 140KB
- available memory
- 12:00:00.15 SYS COM1 present
- 12:00:00.20 SYS COM2 present
- 12:00:03.11 MSG Start test of lower-to-upper
- 12:03:14.03 MSG Sent q, received ~. -- FAILURE
- 12:04:01.53 MSG Sent z, received z. -- FAILURE
- 12:05:34:23 MSG Received 999 correct characters.
- 12:05:45:11 MSG Test Complete
-
- Listing 1
- /*Program to turn small letters into Caps.*/
- /*R. McLaughlin*/
-
-
- /*Includes*/
- #include <stdio.h>
- #include <string.h>
- #include <ctype.h>
- /*Data Structures*/
- #define FALSE 0
- #define FOREVER 1
- #define LOWER "a"
- #define UPPER "z"
- #define MASK 0x20
- int input;
-
- /*Code*/
- main()
- {
- while(FOREVER)
- {
- input=getchar();
- if (input>LOWER)&&(input<UPPER)
- {
- input=input^MASK;
- }
- printf(input);
- }
- }
-
-
- Listing 2
- /*Program Test program to turn small letters
- into Caps.*/
- /*R. McLaughlin*/
-
- /*Includes*/
- #include <stdio.h>
- #include <string.h>
- #include <ctype.h>
- #include <stdlib.h>
- #include <cm.h> /*CHECK*MATE include*/
- #include <setups.h> /*routines to set up serial port*/
- #include <log.h> /*routines to log on and off remote
- system*/
- /*Data Structures*/
- #define FALSE 0
- int i, correct;
- char char_in[2], char_out [2];
- char output[81];
- char msg[100];
-
- /*Code*/
- main()
- {
- CM_log("Start test of lower-to-upper");
- /*Put start message in test log.*/
- Set_line(); /*implementation dependent*/
- Log_in(); /*implementation dependent*/
- for(i=0;i<1000;i++)
- {
- while(isprint(char_out)=FALSE)
- {
-
- char_out= char rand(); /*send only printable
- characters.*/
- }
- TX_chr(DEV1,char_out);
- TX_chr(DEV1,"\n");
- RX_chr(DEV1,char_in);
- if (islower(char_out)!=FALSE))
- {
- if((isupper(char_in)==FALSE)
- (tolower(char_in)==char_out))
- {
- sprintf(msg,"Sent %c, received %c. --
- FAILURE",char_out,char_in);
- CM_log(msg);
- }
- else
- {
- correct++;
- }
- else
- {
- if(char_out==char_in)
- {
- correct++;
- }
- else
- {
- sprintf(msg,"Sent %c, received %c.
- -- FAILURE",char_out,char_in);
- CM_log(msg);
- }
- }
- }
- sprintf(msg,"Received %d correct
- characters.",correct);
- CM_log(msg);
- CM_log("Test Complete");
- Log_off(); /*implementation dependent*/
- Reset_devices(); /*implementation dependent*/
- exit(0);
- }
-
- Note: Implementation dependent code is not shown.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- A Login Shell For MS-DOS
-
-
- Leor Zolman
-
-
- Leor Zolman bought his first microcomputer (an IMSAI 8080) while in high
- school in L.A., carried it to M.I.T., withdrew, and wrote the BDS C compiler
- with it in assembly language. That was enough assembly language hacking to
- last a lifetime, so now he enjoys UNIX/Xenix system administration, article
- writing, and raising his newborn daughter Katelyn. You can reach him at
- leor@rdpub. com or uunet!bdsoft!rdpub!leor.
-
-
- Generic DOS is a single-user, single-process operating system. Only one person
- can directly use a particular client (as opposed to network server) DOS system
- interactively at a time, and that user may only run one program at a time.
- If a DOS system is used by more than one person, users will quickly discover
- that there are no built-in file management mechanisms for associating
- individual files with particular users. The users must keep track of their own
- files (if they understand the DOS directory structure well enough), or all the
- software applications must manage file ownership, perhaps by prompting users
- for their identity and then associating users with their own uniquely-named
- directories or maintaining ownership data in special system files.
-
-
- Logging In
-
-
- In contrast, systems designed to support multiple users (whether
- simultaneously via multi-processing or not), such as the UNIX family of
- operating systems, maintain file ownership information for every program and
- data file on the system. These systems always require a user to log in at the
- beginning of any session. When logging in, a user must provide a public user
- id along with an optional, secret password before he can use the system. The
- user id is typically an alphanumeric string derived from the user's name
- (although some systems, such as CompuServe, use a numeric format). The
- password is a text string known only to the user and protected by the system.
- Once a user has given an acceptable login id and password, the system sets
- some globally-accessible variables (so that other software on the system can
- identify the user) and executes an initial command sequence designed
- exclusively for that user. (On UNIX systems, the login program generally
- starts up one of several available command processors, and that command
- processor becomes responsible for executing customized startup commands.)
- I wrote the program presented in this article to make some rudimentary
- multi-user (although not multi-processing) system features available under
- DOS. The program is named login, for the UNIX facility after which it has been
- patterned. As with the UNIX version, a password file containing information
- about each legal user must be present somewhere on the system. Password files
- typically contains one physical ASCII line of information for each user,
- including at least the user's id and optional password. My version supports
- only these two fields, while the UNIX version contains other fields for
- controlling features not supported by DOS.
- This DOS login offers at least two useful features, even on a single-user (at
- a time) system: security and simplified user interface.
- Login is a start toward preventing unauthorized use of a machine, although
- there probably aren't any 100 percent-reliable security measures for DOS. (In
- fact, my friend Jay Sage of ZCPR3 fame claims perhaps justifiably that the
- ZCPR3 system -- an eight-bit derivative of CP/M -- is inherently much more
- secure an operating system than DOS could ever become.)
- Probably the more practical benefit of login is the ability to simplify the
- user interface for users who are "DOS illiterate." The user doesn't need to
- know about directories and command prompts. All he needs to remember is his
- login id, password, and how to use the applications his customized startup
- sequence launches for him.
-
-
- What the User Sees
-
-
- From the user's point of view, my login, as well as UNIX's, behaves in the
- following manner. When invoked, login prints the string
- login:
- and waits for the user to enter a login id. When the user presses return,
- login checks to see if the user entered a valid id. If the login-id is
- correct, and the password file contains no password associated with that user,
- then the login is approved. (Obviously, security can't be an issue under such
- a configuration.) If the login id requires a password, or even if the login id
- was invalid, login first displays
- password:
- then turns off keyboard echoing and waits for the user to enter a password. By
- prompting for a password even when the login id was invalid, login doesn't
- yield information concerning valid login ids.
- After the user enters a password and presses the return key, login checks that
- the password is correct for the given user id. If so, then the login is
- approved. If it isn't, or if the user id was unrecognized, login says
- login incorrect.
- and repeats the whole process with
- login:
- This pattern continues until a login is approved. You can exit the program
- only by logging in correctly or rebooting the machine. If the machine has a
- bootable floppy drive, then you can bypass login by inserting a floppy boot
- disk (that didn't have login as part of the startup in AUTOEXEC. BAT) and
- rebooting -- so much for bullet-proof security. By configuring the floppy as
- B: on some systems, however, or if used on systems without any floppies, even
- most DOS-literate users would be compelled to play the login game.
-
-
- Starting Up the User
-
-
- Once the user enters the correct login sequence, login performs two more tasks
- to get the session underway.
- First login writes the user's id string into the special file CURRENT_USER in
- the STARTUP_DIR directory (these names are all #defined constants you may
- modify in the source code.) Applications can read the file and determine the
- current user.
- Second, login looks for a "startup file" named USER.BAT where USER is the
- user's login id, in the STARTUP_DIR directory. If found, the file is passed to
- COMMAND.COM (DOS's command processor) for execution via the system() library
- function. This startup file would be the place to put the name of the
- command(s) you want executed when that user logs in; the command might be an
- individual application, a menu system, or some other kind of program.
- To maintain security, the last line of the user's startup file should specify
- the login command itself, to close the loop.
- For another blow to the security issue, remember that most major applications
- have commands that allow "shell escapes" to DOS...and that most applications
- in general can be brought down hard (i.e., crashed) without too much effort,
- presenting the user with an invitation from DOS to press Control-C to abort
- the current batch file and return to a system prompt. Sigh.
-
-
- How It Works
-
-
- For console I/O, login contains several functions that talk directly to the
- low-level console interface routines available via DOS's BDOS to disable the
- interrupt keys Control-C / Control-Break.
- zgetch() reads a character from the keyboard, mapping carriage returns to
- newline characters for convenience. zgets() reads an entire line from the
- keyboard into a character buffer provided as a parameter, using zgetch() to
- read characters. The parameter echo to zgets() controls whether or not the
- keystrokes are echoed back to the screen as they are typed. The format of the
- line collected up by zgets() is the same as for the standard gets() function,
- i.e., null-terminated with no newline character at the end.
- zputs() writes a line to the screen. The library function putch() is used to
- write each character, and newlines are expanded to CR-LF sequences.
- The main program begins by reading in all information from the password file
- into the array of structures named users. As each line is read in from the
- file using fscanf(), the name and passwd elements of users are assigned the
- first and second strings on the line, respectively. If at least one string is
- not found on a line, the program assumes the end of file has been reached (see
- line 67). After each line has been loaded successfully, the loop counter
- variable nusers is incremented.
- If debugging is enabled, lines 69-73 display all the password information as
- it is read from the file. To enable debugging, insert the line
-
- #define DEBUG 1
- at the top of the source file.
- Once all password information has been loaded (and lines 75-76 have verified
- that at least one valid user name is present), the password file is closed and
- the main loop is started. At this point, the variable nusers tells you how
- many records were found in the password file.
- Lines 82 and 83 prompt for a user id and read in a line of text from the
- console into the character buffer named linbuf, with keystroke echo enabled.
- Lines 85-87 loop through the list and compare the strings to see if the
- entered string matches any of the known user names. A match forces an
- immediate break out of the loop, leaving the counter variable i equal to the
- index into users of the matching user id (between 0 and nusers -- 1.) If no
- matching id was found, i ends up with the value of nusers.
- Lines 90-91 handle the case in which no password is required by skipping over
- the password checking code. Lines 90-91 skip the password checking code if and
- only if the following two conditions are met:
- a valid user id was entered (i != nusers), and
- the password for that user is a null string (!*users [i].passwd).
- Otherwise, password checking springs into action. The password prompt is
- displayed and a line of text is read from the keyboard again, but this time
- with the echo turned off (line 94.)
- Another two conditions must be met for the entered password to be accepted as
- valid:
- the user id supplied above must be recognized (i != nusers), and
- the entered string must match the password from the password file.
- Unless both conditions are met, the incorrect login message is displayed and
- the main loop keeps repeating.
-
-
- Upon Success
-
-
- We reach line 101 after a correct login sequence. Login prints a few blank
- lines for the sake of aesthetics, then lines 103-110 attempt to create the
- CURRENT_USER file and write out to it the name of the user who just logged in.
- Finally, lines 112-122 construct the name of the appropriate batch file to run
- for the user. The name is created by starting with the name of the startup
- directory, appending the user id, and then appending the .BAT extension onto
- the end. Login opens the file to see if it is present; if so, the file is
- closed and the filename is passed to the system() function for execution.
-
-
- Logging Out
-
-
- This program is only a starting point. To use the facility most effectively,
- you must still be able to read information from the CURRENT_USER file into
- your applications. Whether used to its capacity or not, secured or otherwise,
- there is still some value in being able to customize each user's initial
- environment to suit his or her needs in the most user-friendly way.
-
-
- Exercise
-
-
- As written, login writes a file (specified by CURRENT_USER) containing the
- name of the active user. Another way to identify the current user to the
- system is to set an environment variable, say CURUSER, to the user's id. This
- method would be faster to execute, but you would then need the appropriate
- tools to access the environment information from the application programs.
- Using the Master Environment Package (from CUJ 11/89), modify login to set an
- environment variable instead of (or in addition to) writing a file to disk.
-
- Listing 1
- 1: /*
- 2: * Login management program for DOS
- 3: *
- 4: * Written by Leor Zolman, 5/1/89
- 5: *
- 6: * Usage (typcially in autoexec.bat):
- 7: * login
- 8: *
- 9: * Control file (PASSWD_FILE) format:
- 10: *
- 11: * -----------------------------------
- 12: * name [password]
- 13: * name [password]
- 14: * .
- 15: * .
- 16: * .
- 17: * -----------------------------------
- 18: *
- 19: * The directory STARTUP_DIR should contain a batch
- 20: * file for each user, named name.BAT. Upon successful
- 21: * login, the batch file named for the user will be
- 22: * be executed.
- 23: * The file named by CURRENT_USER (c:\etc\startup\user.id
- 24: * as configured below) will be written containing the
- 25: * user id after a successful login.
- 26: */
-
- 27:
- 28: #include <stdio.h>
- 29: #include <conio.h>
- 30: #include <stdlib.h>
- 31: // name of startup batch directory, current
- 32: // id file, and password control files:
- 33: #define STARTUP_DIR "c:\\etc\\startup\\"
- 34: #define CURRENT_USER STARTUP_DIR"user.id"
- 35: #define PASSWD_FILE STARTUP_DIR"passwd.dat"
- 36:
- 37:
- 38: #define MAXUSERS 100 // Max number of different users
- 39: #define MAXLINE 100 // For login line input buffer
- 40:
- 41: #define ECHO 1 // Parameters to zgets()
- 42: #define NOECHO 0
- 43:
- 44: char *zgets(char *buffer, int echo); // prototypes
- 45: int zputs(char *str);
- 46:
- 47: struct { // name/password structure
- 48: char name[15];
- 49: char passwd[15];
- 50: } users[MAXUSERS];
- 51:
- 52: void main()
- 53: {
- 54: int i;
- 55: FILE *fp;
- 56: int nusers;
- 57: char linbuf[MAXLINE];
- 58: // open password file
- 59: if ((fp = fopen(PASSWD_FILE, "r")) == NULL)
- 60: exit(cprintf("Can't open %s\a\n", PASSWD_FILE));
- 61:
- 62: // read in user name/password data
- 63: for (nusers = 0; nusers < MAXUSERS; nusers++)
- 64: { // default to null password:
- 65: *users[nusers].passwd = '\0';
- 66: if (fscanf(fp, "%s %s", users[nusers].name,
- 67: users[nusers].passwd) < 1) // scan a line
- 68: break; // break if empty
- 69: #if DEBUG
- 70: else // for debugging, show data
- 71: cprintf("read user name: \"%s\", password: \"%s\"\n",
- 72: users[nusers].name, users[nusers].passwd);
- 73: #endif
- 74: }
- 75: if (!nusers)
- 76: exit(zputs("No valid entries in log file.\a\n"));
- 77:
- 78: fclose(fp); // close password file
- 79:
- 80: while (1) // mail loop
- 81: {
- 82: zputs("login: "); // initial prompt
- 83: zgets(linbuf, ECHO); // get user id w/echo
- 84:
- 85: for (i = 0; i < nusers; i++) // look it up
-
- 86: if (!strcmp(users[i].name, linbuf))
- 87: break;
- 88:
- 89: // found user id. need password?
- 90: if (i != nusers && !*users[i].passwd)
- 91: break; // if not, don't prompt
- 92:
- 93: zputs("\npassword: "); // prompt for password
- 94: zgets(linbuf, NOECHO); // read w/o echo
- 95: if (i != nusers && !strcmp(linbuf, users[i].passwd))
- 96: break; // if correct, break out of loop
- 97:
- 98: zputs("\nlogin incorrect.\n");
- 99: }
- 100:
- 101: zputs("\n\n"); // success!
- 102: // write id file
- 103: if ((fp = fopen(CURRENT_USER, "w")) == NULL)
- 104: cprintf("Couldn't create %s\a\n", CURRENT_USER);
- 105: else
- 106: {
- 107: if (fputs(users[i].name, fp) == EOF)
- 108: cprintf("Couldn't write to %s\n", CURRENT_USER);
- 109: fclose(fp);
- 110: }
- 111:
- 112: strcpy(linbuf, STARTUP_DIR); // construct startup batch
- 113: strcat(linbuf, users[i].name); // filename
- 114: strcat(linbuf, ".bat");
- 115: if ((fp = fopen(linbuf, "r")) != NULL) // is one there?
- 116: {
- 117: fclose(fp); // yes. close it up.
- 118: if (system(linbuf)) // attempt to run it
- 119: cprintf("Couldn't execute %s\a\n", linbuf);
- 120: }
- 121: else
- 122: cprintf("Couldn't find %s\a\n", linbuf);
- 123: }
- 124:
- 125:
- 126: /*
- 127: * function zgets():
- 128: * Read a string from the console with optional echo,
- 129: * and all Ctrl-C / Ctrl-Breka checks disabled:
- 130: */
- 131:
- 132: char *zgets(char *str, int echo)
- 133: {
- 134: char c, *save = str; // save address of buffer
- 135:
- 136: while ((c = zgetch()) != '\n') // read a char
- 137: {
- 138: *str++= c;
- 139: if (echo) // echo if required
- 140: putch(c);
- 141: }
- 142: *str = '\0'; // terminate string
- 143: return save;
- 144: }
-
- 145:
- 146:
- 147: /*
- 148: * function zgetch():
- 149: * Read a character from the keyboard, without echo,
- 150: * performing newline conversion:
- 151: */
- 152:
- 153: int zgetch()
- 154: {
- 155: char c;
- 156:
- 157: c = bdos(7,0,0); // Use a direct BDOS call
- 158: return (c == '\r') ? '\n' : c; // Convert CR to newline
- 159: }
- 160:
- 161:
- 162: /*
- 163: * function zputs():
- 164: * Print a string to the console, with newlines expanded:
- 165: */
- 166:
- 167: int zputs(char *str)
- 168: {
- 169: char c;
- 170:
- 171: while (c = *str++)
- 172: {
- 173: if (c == '\n')
- 174: putch('\r');
- 175: putch(c);
- 176: }
- 177: return 0;
- 178: }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Standard C
-
-
- The Header <limits.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 Standard C, which he co-authored with Jim
- Brodie. You can reach him at pjp@plauger.uunet.
-
-
-
-
- History
-
-
- One of the first attempts at standardizing any part of the C programming
- languages began in 1980. It was begun by an organization then called
- /usr/group, now called Usenix. As the first organization founded to promote
- UNIX commercially, /usr/group had a stake in vendor-independent standards.
- Technical developments couldn't simply go off in all directions, nor could
- they be dictated solely by AT&T. Either way, it was hard to maintain an open
- marketplace.
- So /usr/group began the process of defining what it means to call a system
- UNIX or UNIX-like. They formed a standards committee that focused, at least
- initially, on the C programming environment. That's where nearly all
- applications were written, anyway. The goal was to describe a set of C
- functions that you could expect to find in any UNIX-compatible system. The
- descriptions, of course, had to be independent of any particular architecture.
- A chunk of what /usr/group described was the set of C-callable functions that
- let you access UNIX system services. An even larger chunk, however, was the
- set of functions common to all C environments. That larger chunk served as the
- basis for the language portion of the C Standard. Since Kernighan and Ritchie
- chose not to discuss the library except in passing, the /usr/group standard
- was of immense help to X3J11. It saved us many months, possibly even years, of
- additionl labor.
- As an aside, the /usr/group effort served another very useful purpose. IEEE
- committee 1003 was formed to turn this industry product into an official
- standard. The IEEE group turned over responsibility for the system-independent
- functions to X3J11 and focused on the UNIX-specific portion. You know the
- resultant standard today as IEEE 1003.1, a.k.a. POSIX.
- Part of building an architecture-independent description is to recognize what
- changes across machines. You want to avoid any unnecessary differences, to be
- sure. The rest you want to identify and to circumscribe. Some critical value
- might change when you move an application program to another flavor of UNIX.
- So you give it a name. You lay down rules for testing the named value in a
- program. And you define the limits that the value can range between.
- A long-standing tradition in C is that scalar data types are represented in
- ways natural to each machine. The fundamental type int is particularly
- elastic. It wants to be a size that supports efficient computation, at least
- within broad limits. That may be great for efficiency, but it's a real
- nuisance for portability.
- /usr/group invented the standard header <limits.h> to capture many important
- properties that can change across architectures. It so happens that this
- header deals exclusively with the ranges of values of integer types. That was
- all that /usr/group chose to address. When X3J11 decided to add similar data
- on the floating types, we elected not to overwhelm the existing contents of
- <limits.h>. Instead, we added the standard header <float.h>. Perhaps we should
- have renamed the existing standard header <integer.h>, but we didn't. Tidiness
- yielded to historical continuity.
-
-
- Using <limits.h>
-
-
- You can use <limits.h> one of two ways. The simpler way assures that you do
- not produce a silly program. Let's say, for example, that you want to
- represent some signed data that ranges in value between VAL_MIN and VAL_MAX.
- You can keep the program from compiling incorrectly by including in the text:
- #include <assert.h>
- #include <limits.h>
- #if VAL_MIN < INT_MIN \
- INT_MAX < VAL_MAX
- #error values out of range
- #endif
- You can safely store the data in variables declared with type int if the error
- directive is skipped.
- A more elaborate way to use <limits.h> is to control the choice of types in a
- program. You can alter the example above to read:
- #include <assert.h>
- #include <limits.h>
- #if VAL_MIN < LONG_MIN \
- LONG_MAX < VAL_MAX
- typedef double Val_t;
- #elif VAL_MIN < INT_MIN \
- INT_MAX < VAL_MAX
- typedef long Val_t;
- #else
- typedef int Val_t;
- #endif
- You then declare all variables that must hold this range of values as having
- type Val_t. The program will use the computationally most efficient type for a
- given target environment.
- The presence of <limits.h> is also designed to discourage an old programming
- trick that is extremely non-portable. Some programs attempted to sniff out the
- properties of the target environment by writing tricky if directives, as in:
- #if (-1 + 0x0) >> 1 > 0x7fff
- /* must have ints greater than 16 bits */
- .....
- #endif
-
- This code assumes that whatever arithmetic the preprocessor performs is the
- same as what occurs in the execution environment. Those of us who deal heavily
- with cross compilers know well that the translation environment can differ
- markedly from the execution environment. For tricks like this one to work, the
- C Standard would have to require that the translator mimic the execution
- environment very closely. And compiler families with a common front end would
- have to adapt translation-time arithmetic to suit the target.
- X3J11 discussed such requirements at length. In the end, we decided that the
- preprocessor was not the creature to burden with such stringent requirements.
- The translator must closely model the execution environment in many ways, to
- be sure. It must compute constant expressions -- the things you use to
- initialize static data objects, for example -- to at least as wide a range and
- precision as the target. But it can largely define its own internal
- environment for the arithmetic within if and elif directives.
- So to test the execution environment you can't do experiments on the
- preprocessor. You must include <limits.h> and test the values of the macros it
- provides.
-
-
- What The Standard Says
-
-
- Here is what the Standard has to say about <limits.h>. The library portion
- contains only a brief reference:
-
-
- 4.1.4 Limits <float.h> and <limits.h>
-
-
- The headers <float.h> and <limits.h> define several macros that expand to
- various limits and parameters.
- The macros, their meanings, and the constraints (or restrictions) on their
- values are listed in 2.2.4.2. [end of extract]
- That's it. For the meat, you have to go back to the environment section:
-
-
- 2.2.4.2 Numerical Limits
-
-
- A conforming implementation shall document all the limits specified in this
- section, which shall be specified in the headers <limits.h> and <float.h>.
-
-
- 2.2.4.2.1 Sizes of Integral Types
-
-
-
-
- <limits.h>
-
-
- The values given below shall be replaced by constant expressions suitable for
- use in #if preprocessing directives. Moreover, except for CHAR_BIT and
- MB_LEN_MAX, the following shall be replaced by expressions that have the same
- type as would an expression that is an object of the corresponding type
- converted according to the integral promotions. Their implementation-defined
- values shall be equal or greater in magnitude (absolute value) to those shown,
- with the same sign.
- number of bits for smallest object that is not a bit-field (byte)
- CHAR_BIT 8
- minimum value for an object of type signed char
- SCHAR_MIN -127
- maximum value for an object of type signed char
- SCHAR_MAX +127
- maximum value for an object of type unsigned char
- UCHAR_MAX 255
- minimum value for an object of type char
- CHAR_MIN see below
- maximum value for an object of type char
- CHAR_MAX see below
- maximum number of bytes in a multibyte character, for any supported locale
- MB_LEN_MAX 1
- minimum value for an object of type short int
- SHRT_MIN -32767
- maximum value for an object of type short int
- SHRT_MAX +32767
- maximum value for an object of type unsigned short int
- USHRT_MAX 65535
- minimum value for an object of type int
- INT_MIN -32767
- maximum value for an object of type int
- INT_MAX +32767
- maximum value for an object of type unsigned int
-
- UINT_MAX 65535
- minimum value for an object of type long int
- LONG_MIN -2147483647
- maximum value for an object of type long int
- LONG_MAX +2147483647
- maximum value for an object of type unsigned long int
- ULONG_MAX 4294967295
- If the value of an object of type char is treated as a signed integer when
- used in an expression, the value of CHAR_MIN shall be the same as that of
- SCHAR_MIN and the value of CHAR_MAX shall be the same as that of SCHAR_MAX.
- Otherwise, the value of CHAR_MIN shall be 0 and the value of CHAR_MAX shall be
- the same as that of UCHAR_MAX.9
- Footnote
- 9. See 3.1.2.5 [end of extract]
- The only significant addition to this header from the days of /usr/group is
- the addition of MB_LEN_MAX. I will discuss multibyte characters at length in a
- future column.
- Several people reviewing the Standard complained that names such as USHRT_MAX
- are barbaric. It is silly to omit a single vowel in the interest of terseness,
- particularly in this age of 31-character (or longer) names. I can only plead,
- on behalf of X3Jll, the same excuse we gave for not changing the name of the
- header itself. We couldn't justify abandoning prior art just to be a bit
- tidier. Besides, fixing such barbarisms only in this place is like eating one
- peanut.
-
-
- Implementing <limits.h>
-
-
- The only code you have to provide for this header is the header itself. All
- the macros defined in <limits.h> are testable within an if directive and are
- unlikely to change during execution. (The same is not true of most of the
- macros defined in <float.h>.)
- Most modern computers have eight-bit bytes, two-byte shorts, and four-byte
- longs. There are several common variations on this principal theme:
- An int is either two or four bytes.
- A char has the same range of values as either signed char or unsigned char.
- Signed values are encoded most frequently in 2's complement, which has only
- one form of zero but one negative value that has no corresponding positive
- value. Less common encodings are 1's complement and signed magnitude, which
- have two forms of zero but no extra negative value.
- The number of bytes for a single multibyte character can be any value greater
- than zero.
- I found it convenient, therefore, to write a version of <limits.h> that
- expands to any of these common forms. It includes, as needed, a configuration
- file called <yvals.h>. Among other things, this file defines the macros:
- _ILONG -- nonzero if a long has four bytes
- _CSIGN -- nonzero if a char is signed
- _2C -- 1 if the encoding is 2's complement, else 0
- _MBMAX -- the worst-case length of a single multibyte character.
- Listing 1 shows the code for <limits.h>.
- The use of the macro _2C obscures an important subtlety. On a 2's-complement
- machine, you cannot simply write the obvious value for INT_MIN. Why not? On a
- 16-bit machine, for example, the sequence of characters -32768 parses as two
- tokens -- a minus sign and the integer constant with value 32,768. The latter
- has type long because it is too large to represent properly as type int.
- Negating this value doesn't change its type. The Standard requires, however,
- that INT_MIN have type int. Otherwise, you can be astonished by the behavior
- of a statement as innocent looking as:
- printf("range is from %d to %d\n",
- INT_MIN, INT_MAX);
- The only safe thing is to sneak up on the value by writing an expression such
- as (-32767-1). Given the way I chose to parametrize <limits.h>, you get this
- trickery for free.
- One other subtlety should not be overlooked. I made the point earlier that
- preprocessor arithmetic need not model that of the execution environment. You
- can, in principle, compile on a host with 32-bit longs for a target with
- 36-bit longs. Nevertheless, the host is obliged to get the values in
- <limits.h> right. That means that it must do preprocessor arithmetic to at
- least 36 bits. The latitude spelled out for implementors by X3J11 isn't so
- broad after all.
-
-
- Testing <limits.h>
-
-
- Listing 2 is a brief sanity check you can run on <limits.h>. It is by no means
- exhaustive, but it does tell you whether the header is basically sane.
- Note that all the action occurs at translation time. That's because all the
- macros must be usable within if directives. If this test compiles, it will
- surely run, print its success message, and exit with happy status.
- You might try this test on your favorite compiler. I can only comment that I
- have known it to fail on some popular offerings.
-
- Listing 1
- /* limits.h standard header -- 8-bit version
- * copyright (c) 1991 by P.J. Plauger
- */
- #ifndef _LIMITS
- #define _LIMITS
-
- #ifndef _YVALS
- #include <yvals.h>
- #endif
- /* char properties */
- #define CHAR_BIT 8
- #if _CSIGN
- #define CHAR_MAX 127
- #define CHAR_MIN (-127-_2C)
- #else
-
- #define CHAR_MAX 255
- #define CHAR_MIN 0
- #endif
- /* int properties */
- #if _ILONG
- #define INT_MAX 2147483647
- #define INT_MIN (-2147483647-_2C)
- #else
- #define INT_MAX 32767
- #define INT_MIN (-32767-_2C)
- #endif
- /* long properties */
- #define LONG_MAX 2147483647
- #define LONG_MIN (-2147483647-_2C)
- /* multibyte properties */
- #define MB_LEN_MAX_MBMAX
- /* signed char properties */
- #define SCHAR_MAX 127
- #define SCHAR_MIN (-127-_2C)
- /* short properties */
- #define SHRT_MAX 32767
- #define SHRT_MIN (-32767-_2C)
- /* unsigned properties */
- #define UCHAR_MAX 255
- #define UINT_MAX 4294967295
- #define ULONG_MAX 4294967295
- #define USHRT_MAX 65535
- #endif
-
-
- Listing 2
- /* test limits macros
- * copyright (c) 1991 by P.J. Plauger
- */
- #include <limits.h>
- #include <stdio.h>
-
- /* test basic properties of limits.h macros
- */
- int main()
- {
- #if CHAR_BIT < 8 CHAR_MAX < 127 0 < CHAR_MIN \
- CHAR_MAX != SCHAR_MAX && CHAR_MAX != UCHAR_MAX
- #error bad char properties
- #endif
- #if INT_MAX < 32767 -32767 < INT_MIN \
- INT_MAX < SHRT_MAX
- #error bad int properties
- #endif
- #if LONG_MAX < 2147483647 \
- -2147483647 < LONG_MIN \
- LONG_MAX < INT_MAX
- #error bad long properties
- #endif
- #if MB_LEN_MAX < 1
- #error bad MB_LEN_MAX
- #endif
- #if SCHAR_MAX < 127 -127 < SCHAR_MIN
- #error bad signed char properties
-
- #endif
- #if SHRT_MAX < 32767 -32767 < SHRT_MIN \
- SHRT_MAX < SCHAR_MAX
- #error bad short properties
- #endif
- #if UCHAR_MAX < 255 UCHAR_MAX <= 2 * SCHAR_MAX
- #error bad unsigned char properties
- #endif
- #if UINT_MAX < 65535 UINT_MAX <= 2 * INT_MAX \
- UINT_MAX < USHRT_MAX
- #error bad unsigned int properties
- #endif
- #if ULONG_MAX < 4294967295 \
- ULONG_MAX <= 2 * LONG_MAX \
- ULONG_MAX < UINT_MAX
- #endif
- #if USHRT_MAX < 65535 USHRT_MAX <= 2 * SHRT_MAX \
- USHRT_MAX < UCHAR_MAX
- #error bad unsigned short properties
- #endif
- puts("SUCCESS testing <limits.h>");
- return (0);
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Doctor C's Pointers(R)
-
-
- Puzzles, Part 4
-
-
-
-
- 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 uunet! aussie! rex or aussie! rex@uunet.uu.net.
-
-
- This month we'll continue with our "quality of implementation" puzzles. Try to
- debug them yourself first and see what messages your compiler produces.
-
-
- The Puzzles
-
-
- 1. A typical example of C.
- /* 1*/#include <stdio.h>
-
- /* 3*/main()
- /* 4*/{
- /* 5*/ char *pc;
-
- /* 7*/ printf("Enter up to 3
- characters: ");
- /* 8*/ scanf("%3s", pc);
- /* 9*/ printf("Input is %s\n",
- pc);
- /*10*/}
-
- Enter up to 3 characters: asd
- Input is asd
- I've often seen publications introduce C using an example of a typical C
- program such as that above. It's typical because it has bugs. I also see it in
- many of my student's projects.
- 2. So why shouldn't it work?
- /* 1*/#include <stdio.h>
-
- /* 3*/main()
- /* 4*/{
- /* 5*/ char *pc = "abcdef";
-
- /* 7*/ *pc = 'X';
-
- /* 9*/ printf("pc points to
- %s\n", pc);
- /*10*/}
-
- pc points to Xbcdef
- This program runs as shown on many systems. It also dies miserably on others,
- and both outcomes are acceptable, at least by ANSI C.
- 3. Why isn't 'end' recognized?
- /* 1*/#include <stdio.h>
-
- /* 3*/main()
- /* 4*/{
-
- /* 5*/ char str[4];
-
- /* 7*/ printf("Enter up to 3
- characters: ");
- /* 8*/ scanf("%3s", str);
-
- /*10*/ if (str == "end")
- /*11*/ printf("'end' entered\n");
- /*12*/ else
- /*13*/ printf("'end' was not entered\n");
- /*14*/}
-
- Enter up to 3 characters: 123
- 'end' was not entered
-
- Enter up to 3 characters: end
- 'end' was not entered
- 4. More on string literals.
- /* 1*/ #include <stdio.h>
-
- /* 3*/ main()
- /* 4*/ {
- /* 5*/ if ("abc" == "abc")
- /* 6*/ printf("Equal\n");
- /* 7*/ else
- /* 8*/ printf("Not Equal\n");
- /* 9*/ }
-
- The two possible outcomes are:
-
- Not Equal
- Equal
- 5. Hey, who broke my sizeof?
- /* 1*/ #include <stdio.h>
-
- /* 3*/ main()
- /* 4*/ {
- /* 5*/ static int j[10];
- /* 6*/ void f(int []);
-
- /* 8*/ f(j);
- /* 9*/ }
-
- /*11*/ void f(int x[])
- /*12*/ {
- /*13*/ int i;
-
- /*15*/ for (i = 0; i < sizeof(x)/sizeof(x[0]); ++i)
- /*16*/ printf("i = %d\n", i);
- /*17*/}
-
- i = 0 /* small memory model */
-
- i = 0 /* large memory model */
- i = 1
-
- i = 0 /* 32-bit mode compiler */
- This very common example attempts to use sizeof on a formal array parameter to
- determine the number of elements in that array. Unfortunately, this construct
- compiles without error and gives you something other than what you initially
- expect.
-
-
-
- The Solutions
-
-
- 1. It is very unlikely this will fail to execute correctly on DOS. However,
- increasing significantly the number of characters accepted will certainly
- increase the chances of actual undefined behavior. Many of my seminars are
- conducted using VAX/VMS, a multi-user memory protected operating system. In
- such environments, weird behavior is likely to occur from the start.
- scanf expects a pointer to char and that's what we give it. scanf then
- proceeds to read in characters storing them at the location pointed to by that
- pointer. The problem is, just where does that pointer point? Being of
- automatic storage duration, pc's initial value is undefined -- it could be any
- bit pattern -- and may look like a valid or invalid address. So scanf tries to
- store the characters read in some arbitrary location. Without memory
- protection, DOS allows you to overwrite any location in memory, including DOS
- itself and device registers, particularly in the larger memory models. On
- VAX/VMS, if pc contains an address outside the addresses allocated to this
- program, or in the range 0-511, or to part of the program that is located in a
- read-only program section (such as instructions and const data objects), a
- memory access violation results.
- Several compilers did give useful warnings when asked.
- line 8 - Warning: Possible use
- of 'pc' before assigned a value
-
- line 9 - Warning: Possible use
- of 'pc' before assigned a value
-
- line 9 - Warning! 'pc' has been referenced but
- never assigned a value
- The (undefined) value of pc was used in two places, lines 8 and 9. One
- compiler correctly warned about both but the other warned only about line 9.
- 2. By initializing pc to a string, the code does not make it obvious that in
- *pc = 'X' we are attempting to modify the first character of a string literal.
- Perhaps if we wrote "abcdef"[0] = 'X' that would be more obvious. (By the way,
- this is a perfectly well-formed expression that achieves the same result.) The
- problem is even more obscure if pc is passed to a function that intends to
- modify what pc points to (as the first argument to strcpy, for example).
- Based on its name you would expect a string literal to literally be stored
- exactly as written. In fact, most of us probably really think of string
- literals as being constants; and that's not unreasonable. The problem is that
- over the years some compilers have stored them in read-only memory while
- others placed them in read-write memory. As a result, ANSI C permits either,
- so it's your problem either to avoid intentionally modifying such strings or
- to know which of your target compilers permit this.
- Since DOS has no memory protection capability, DOS-based compilers always
- allow strings to be overwritten. More sophisticated systems either force or
- allow strings to be placed in read-only memory at runtime. (Apparently, 32-bit
- compilers running under DOS extenders can also take advantage of the segment
- protection of the Intel 386 architecture.)
- 3. None of my compilers gave any warnings. However, PC-Lint produced the
- following interesting message:
- line 10 - Warning: Constant value Boolean
- It's telling me that str == "end" is a constant expression which, therefore,
- can be evaluated at compile-time meaning that either the true or false path
- will be forever ignored. Since this presumably was not our intent, what's the
- real problem?
- What we really want to do is to compare the null-terminated character array
- str with the string "end". Now as we know, in almost all cases where we use
- the name of an array (or, in fact, any expression that designates an array)
- that expression is converted to a pointer to the first element. Therefore, the
- expression str becomes &str[0] and the address of an array's element is a
- constant throughout the life of that array. (This is true even for automatic
- arrays. Granted that each time the same auto object is allocated it can be in
- a different place in memory, but for any one of its lives it will stay in the
- same location.) Similarly, "end" becomes &"end"[0] since "end" is the 'name'
- of an unnamed array of 4 chars.
- As a result, (str == "end") becomes (&str[0] == &"end"[0]) which is a constant
- expression. And since, by definition, objects don't overlap in storage (unions
- excepted), the address of any element of one array can never be equal to that
- of an element in a different array. The resulting type of this expression is
- int (by definition) and its value is 0 (false).
- Of course, what we really must use is strcmp(str, "end") == 0.
- 4. In the previous problem we learned that what is really going on is that the
- addresses of the first character in each string is being compared, not the
- value of the strings. So why are the addresses the same with some compilers
- and not others?
- Whether identical string literals occupy the same memory (they are pooled) or
- are treated as being distinct is up to the implementation. It is more work for
- a compiler to compare every string literal token with every other. Most
- compilers simply treat each string as being distinct. A few provide options to
- go either way.
- Consider the following program:
- #include <stdio.h>
-
- main()
- {
- printf("%p %p\n", "abc", "abc");
- }
-
- 1bca:0026 1bca:0026 /* only one
- copy */
- 1B20:0046 1B20:0042 /* two copies
- */
- Compilers then, can support one or more of the four possible combinations of
- string literals being read-only or writeable, and pooled or not.
- 5. When an array is passed to a function, what is actually passed is a pointer
- to its first element. No information is passed about the number of dimensions
- or their sizes. Once the called function takes control, the shape information
- for that array is lost -- the function simply knows it has a pointer to some
- type.
- If we write the function definition in a different, but equivalent, form, the
- problem should be obvious:
- /*11*/ void f(int *x) {...}
- Now we have explicitly admitted that x really is a pointer to the first
- element of the array passed in. Therefore, sizeof(x) gives us the size of that
- pointer, not the underlying array. Now we could use sizeof(*x), but that only
- gives us the size of the object underlying x (an int) not the size of the
- whole underlying array.
- In the small memory model and in 32-bit mode, pointers and ints are the same
- size (16 and 32 bits, respectively). In large mode, pointers are 32 bits and
- ints are 16.
- So how can we solve the problem correctly if sizeof won't do? If the array
- passed were a null-terminated char array we could use strlen. For all other
- array types we would have to pass in the array size as an argument or use a
- global variable, unless there was some special terminating value we could
- check for.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- On The Networks
-
-
- Special Issue: Network News
-
-
-
-
- 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 now has been a year that I have been writing this column for The C Users
- Journal. I think it's time for an update on what the networks are and how you
- can get on the ones that this column references. In last January's issue, I
- discussed the internet vs. the Internet, USENET, Network News, and freely
- distributed software. I will review and update this information before
- crossing into some new ground. Again, this column is my personal view of both
- the history and the current state of the networks. It is not intended to be a
- proper chronological history of USENET or Network News.
-
-
- Network News: A Review
-
-
- In this column, I refer to several USENET Network News groups, including
- comp.sources.unix, comp.sources.misc, comp.sources.games, and alt.sources. I
- mention software, usually in C, that has been posted to these groups that
- might interest CUJ readers. This leads to several questions: What is USENET
- Network News? How do I get to the software? What is the phone number to
- download the software?
- USENET is a moniker given to the UNIX Users Network. It's an outdated name,
- but still survives. USENET started as a small collection of UNIX computers
- connected with UUCP. Today, USENET is an amorphous mass of computers connected
- by a variety of protocols. The computers run different operating systems,
- including many of the popular personal computer operating systems. What makes
- a computer a member of USENET (or at least connected to USENET)? Generally a
- computer is considered connected to USENET if it can exchange electronic mail
- with other computers that are connected to USENET. A rather broad definition,
- and one that has blurred lately as other networks exchange mail with USENET
- computers via gateways. Perhaps it should be restricted to computers where you
- can exchange electronic mail with other computers on USENET without going
- through a gateway. This is a revised version of the older definition of a
- collection of computers that communicate using UUCP.
- Joining USENET is easy. All you do is find a member who is willing to connect
- you via his or her computer. Sort of a Catch-22 -- you need to know who is
- already a member to become one. A partial list of connected sites is published
- only via USENET Network News, which you cannot receive until you are
- connected. Read on, anyway, and I'll present the solution to this Catch-22.
- If USENET is an electronic mail network, what is Network News? Most readers
- might be familiar with the concept of a Bulletin Board System. A BBS is a
- computer that holds a database of messages posted by other members of the
- system. Your computer dials the BBS computer, and you read and post messages.
- Generally these are local systems, and usually small. Message bases range in
- the megabytes of information. One collection of BBSs has formed a worldwide
- organization called Fidonet that exchanges messages between systems, but most
- are small computers run by individuals. Network News is similar and yet
- different from a BBS. With Network News, users post messages, but that is
- where the similarity ends. On a BBS when you post a message, that message sits
- on the local computer to be read by others until it expires and is deleted.
- With USENET Network News the entire message base exists on every computer in
- the network. Thus the message you post is sent to every other computer. You
- read messages on your own computer, even though they were posted by others
- elsewhere.
- How do USENET and USENET Network News differ? USENET is any computer connected
- for electronic mail to other USENET computers. A subset of these computers
- also agree to exchange one or more of the categories of Network News.
- Currently there are about 1,000 different news categories, called newsgroups.
- The newsgroups are broken down into several hierarchies. These include the
- traditional major hierarchies of news, comp, rec, sci, soc and talk; regional
- hierarchies such as na, usa, ba, pa, nj, and specialized hierarchies such as
- bionet, biz, bit, unix-pc, and u3b. The major hierarchies are the widest
- distributed, accessing over several hundred thousand computers worldwide. The
- regional hierarchies are used for messages only of regional interest, such as
- North America (na), the United States (usa), the San Francisco Bay Area (ba),
- the state of Pennsylvania (pa), or New Jersey (nj), just to name a few. The
- specialized hierarchies serve communities with special interests.
- Each hierarchy has its own set of rules, which are enforced by consensus.
- USENET has no governing body, just a set of guidelines that individual
- computer owners or administrators follow as they see fit. It seems to work
- most of the time, as the net runs without too much chaos. There is even a
- hierarchy that runs without rules, called alt.
- You are a recipient of network news if you subscribe to one or more of the
- newsgroups in any of the hierarchies. Some sites only receive a handful of the
- groups, and some receive most or all of the groups. This involves a large
- amount of information -- more than 20 megabytes of new postings every day.
- This is up from about two megabytes of posting per day only two years ago. At
- 20 megabytes per day, each site can only keep a small portion of the feed on
- line at a time, and at that, only a few days worth.
- With so much information coming in each day, it would seem like a lot of work
- just maintaining it, or finding something worthwhile to read. It's not that
- bad. The software to run the news system controls itself almost automatically.
- It includes facilities to send only those groups to which you subscribe, as
- well as expiring old articles to recover space. However, being a major site in
- the USENET Network News distribution system requires a large amount of disk
- space and modem time.
- The source distribution groups may interest readers of this column. These
- groups are generally grouped under the comp hierarchy in a collection called
- sources, thus the names comp.sources.unix and comp.sources.misc. Originally
- comp.sources.unix was for freely distributable sources designed to run on UNIX
- systems. Many of the sources posted there now are also able to run on personal
- computers and other operating systems, but all can run on UNIX. The other
- sources groups currently in the comp hierarchy are: amiga -- for software
- specific to amiga systems; atari.st -- for software specific to these
- computers; games -- restricted to game software which is in turn restricted to
- this group; mac -- software specifically for Apple Macs; misc -- general
- software, not necessarily for UNIX systems; sun -- for Sun Microsystems
- workstations; and x -- software for the X windowing system. This column
- generally restricts itself to the unix, misc, and games groups.
- Authors submit their sources to a moderator, who is the only person allowed to
- post to the group. The moderator bundles the sources for distribution, checks
- that they are complete, and posts them. The moderator also assigns volume and
- issue numbers to each of the postings. A submission might take several issues,
- because an issue is limited to about 60,000 bytes. The moderator also posts
- periodic indices of the sources posted to his group. No discussion is allowed
- in these groups, and only postings approved by the moderator are accepted.
- Because of the high "signal to noise ratio" in these groups, some computer
- sites around the world save the sources for future access. These sites are
- called archive sites. Each archive site decides what groups to archive and how
- long to keep them. Contact these archive sites to obtain the sources mentioned
- in the column. You must contact the archive sites because the individual
- members of USENET, unless they archive these groups, will have deleted the
- sources to make room for the newer postings, usually within a week of the
- original posting.
-
-
- 20Mb A Day
-
-
- A bit of simple math yields some impressive numbers. If your computer
- exchanges Network News with two neighbors (a small site), you are receiving
- 20Mb a day for a full feed, and sending that 20Mb each day to the second site.
- Network News broadcasts all articles to all sites that have not yet received
- them. Any article you get from one site, you send to all others that you are
- connected to and that have not received the article. Now, 20Mb per day on a
- phone line at 2,400 bps (240 characters per second maximum speed) yields
- approximately 24.25 hours on the phone per day. This isn't possible as there
- are only 24 hours in a day. In addition UUCP doesn't achieve the 240 cps
- maximum for 2400 bps due to delays and overhead. This problem is solved by
- sending articles in compressed batches. The compression is usually about 50-70
- percent effective, cutting that 24.25 hours to eight to twelve hours. Still a
- big phone bill. How do sites cut that down even further? Most big sites run
- special modems, such as V.32 (at 960cps) or Telebit Trailblazers (at
- 1,200-1,400cps), which cuts it down to about two to three hours per day. It
- you talk to two sites, expect that to be four to six hours total.
- A major site might exchange news with 20 or more neighbors. They can
- accomplish this several ways -- using a bank of modems, exchanging partial
- feeds of selections from the list of 1,000 newsgroups, or using the Internet.
-
-
- The Internet
-
-
- Modems and serial protocols are not the only way to connect computers. Many
- larger computer sites, including corporations, universities, and government
- installations, have banded together to form a large network called the
- Internet. This network uses the TCP/IP protocol, designed by the Department of
- Defense and its contractors, to communicate over very high-speed links. Usual
- links for Internet sites range from 9,600bps (about 1,000cps), to 56kbps
- (about 5,400 cps), to 1.544Mbps. A full news feed at 1.544 Mbps doesn't take
- very long at all -- just a couple of minutes if it were sent as a big batched
- file transfer. The newest network connections that are the backbone of the
- Internet are switching from 1.544Mbps to 45Mbps, allowing for even greater
- capacity.
- Network News on the Internet uses a special protocol on top of TCP/IP for more
- efficient distribution. This is NNTP or the Network News Transfer Protocol.
- Switching from a backbone network of mostly UUCP connections to using NNTP
- over the Internet has improved the flow of network news and allowed for its
- dramatic growth over the past few years. Just two years ago, an article took
- several days to propagate to all the USENET computers (at that time tens of
- thousands of computers). Now it takes less than a day -- and usually only
- several hours -- to reach most of the hundreds of thousands of computers.
-
-
- The Catch-22 Revisited
-
-
- I promised to explain how you too can join the systems on USENET. It isn't
- that hard, and you can use several approaches.
- First, you can try to find some site near you that is already connected, and
- then connect to them. You might begin with local user groups. One of the
- newsgroups is comp.mail.maps, a collection of a list of sites on USENET. As I
- have offered in the past, if you send me a self addressed envelope with
- sufficient postage, I will run a program I have that will search the maps for
- sites in your area code. Those that live in major cities should provide two
- stamps, other areas need only one stamp. This list will include the site
- contact person for each of the sites in your area code. (Please provide your
- area code for me.) Many of them may not be willing to allow you to connect,
- for various reasons, but usually you can find one who will cooperate. These
- maps only list sites that use the UUCP protocol, so your computer must be able
- to communicate using UUCP. If you have access to electronic mail, it is easier
- for me to respond to electronic mail for the request. Note that you can reach
- me via AT&T Mail, Compuserve, Easynet, MCI Mail, or Sprintmail. Ask your
- network administrator how to send mail to an Internet address.
- A second approach is to connect via a major site that sells reconnect
- services. This would include sites like mine (Datacomp Systems, Inc,
- 215-947-9900), UUNET Communications Services (703- 876-5050), or Portal
- Communications Company (408-973-9111). Each offers UUCP connections for a fee.
- These fees vary by the amount of information (time connected) transferred and
- can range from about $20 a month to several hundred dollars per month.
- A third method is to read network news on another computer that is already
- connected. Several "public access" sites exist. They vary from free, to just a
- couple of dollars a month, to several dollars per hour. Portal is an example
- of one of these public access sites. Another is the Whole Earth 'Lectronic
- Link (415-332-4335). Both charge for their connect time. Public access sites
- also have their own hierarchy of network news and list many almost free
- connections. Generally the number of modems available for customers goes up
- with the price of the connection.
-
-
- Back to USENET
-
-
-
- Okay, enough on News. After all, USENET started with electronic mail. But how
- do you send electronic mail via USENET? If you've read enough of my columns,
- you see in the author's reference section an address called an electronic mail
- address. This is the key to sending mail via USENET. My address is syd@DSl.COM
- as listed in that section. How does that relate to USENET?
- UNIX computers have included mail facilities from almost the beginning. This
- mail allowed for sending messages from one user to another using their login
- names as the address. When UUCP came along, the address was extended to
- include the site name and the login name separated by an '!', as in my UUCP
- address dsinc!syd. However, to send to dsinc!syd, you had to be directly
- connected to dsinc. So dsinc had to be an entry in your UUCP node table in
- order for your computer to call my computer and exchange the mail.
- As the number of computers grew, this method became impractical. So the
- concept of routing was introduced. If you talked (had the site in your UUCP
- site file) to abc, and I talked to abc, then you could send me mail as
- abc!dsinc!syd. Extend this and you end up with the long routes associated with
- UUCP such as abc!def!ghi!jkl!mno!pqr!dsinc!syd. Each is a hop on the message's
- journey from your machine to mine. But how do you determine, in advance, the
- routing that the message needs to take to efficiently travel from your
- computer to mine? Some sites might only exchange mail once a week. If you
- choose one of those as an intermediate hop, the message could be delayed for a
- long time.
- USENET Network News comes to the rescue. The newsgroup comp.mail.maps contains
- periodic postings of information on the connections of each site registered on
- USENET. (Unfortunately, not all sites bother to register, although the process
- is painless and free.) A freely distributable computer program called
- pathalias takes this data and makes a file listing how to get from your site
- to each of the other sites in the maps. It produces a rather large file. With
- this paths file, the mail transport agent (the program that delivers the mail)
- on your computer can route the message for you, speeding it on its way.
- If you only have a couple of connections, there is an easier way. You can
- always send the message to one of your neighbors, who can route it for you.
- This is known as hop routing. If your neighbor cooperates, hop routing can be
- a very effective method. Each neighbor then sends the message to a more
- intelligent neighbor until someone knows how to route it. This method is
- recommended for small sites, as it gives the larger sites a chance to optimize
- the delivery of the messages. The mail software supports hop routing via the
- concept of a smart-host. Your smart-host is a site you send the mail that you
- don't know how to route.
- So far so good, this handles dsinc!syd. What do I do with syd@DSI.COM, which
- is not a UUCP address? UUCP addresses are not guaranteed to be unique. Two
- sites could easily choose the same name, which can and does lead to problems.
- Secondly, not all sites use UUCP for delivery.
- Over the years, the Internet has developed a mail addressing scheme that uses
- the notation user@site, where user is either their login name, or some
- nickname that the site will translate into their login name. Site is a
- dot-separated list of items that make up a fully qualified domain name (FQDN).
- FQDNs are advantageous because they are guaranteed to be unique. Also, they
- don't need to be routed since each site can deliver them using hop routing.
- Some UUCP sites can't handle FQDNs. The maps give a list of sites that can.
- You just forward them to your smart-host or to one of those sites.
- Thanks to the interconnection and speed of the Internet backbone, using FQDNs
- has several advantages. Mail is now delivered with very few hops, making for
- much quicker delivery. The Internet doesn't need maps since it can now
- distribute the lookup over the entire network. A small explanation will show
- why the UUCP domain doesn't always work. My base computer is dsinc.dsi.com. If
- you are somewhere on the Internet, (anywhere in the world) and want to contact
- me at that machine, your computer will do a Domain Name Service query, asking
- who is dsinc.dsi.com. It does this by sending a request to its local (on site)
- program for nameservice queries asking that program who is dsinc.dsi.com. That
- program looks in its in-memory cached information for that name. If it finds a
- match, it returns my address. If it doesn't find a match, it looks for dsi.com
- to see if it finds a match. Again, if no match, it looks for com (one of the
- root domains). There is a fixed list of servers for the com domain, each of
- which is given the registration information for every "second-level domain"
- such as dsi. The nameservice query program asks that server for information on
- dsi.com and gets told which machine to ask to find out about machines in the
- dsi.com domain. It then makes one more request to that machine for
- dsinc.dsi.com's address. In two queries, it has my address. It then connects
- directly to my address, very much the way you dial a phone to connect directly
- to someone else's phone. Thus, every machine on the Internet is a single hop
- from every other.
- So far, so good, but some FQDNs are not on the Internet, including the popular
- compuserve.com. Domains not directly on the Internet list a mail forwarder for
- themselves, which is on the Internet. Their mail is delivered to the mail
- forwarder who handles the final delivery.
-
-
- If It Were That Easy
-
-
- Looks easy. All you need to do is send the mail up the chain to a smart site
- and bingo, it's delivered, right? Wrong, unfortunately. The UUCP world and the
- Internet world have a small problem talking to each other. This problem is
- routing. In the UUCP world, each message is source routed, as in
- abc!dsinc!syd. In the Internet world, each message is directly routed, as in
- user@site. What if you want to send a message to a site off of mine, called
- abc. Would that be abc!user@dsi.com, or abc@dsi.com!user or dsi.com!abc!user,
- or what? There is no standard because there is no consensus of software. Some
- software evolved doing it one way, some another. There is a recommendation but
- no way to enforce that recommendation. On the Internet, if you don't follow
- the rules, you can be disconnected. Sort of like having your phone removed for
- misuse. UUCP hops are cooperative, not by edict of a ruling body. There is no
- standard answer. The recommendation is to use abc!user@dsi.com, but many sites
- will try to send that to the site abc and then from there to user@dsi.com.
- Some sites support the notion of dsi.com!abc!user, and some don't. Others
- pretend to be user@abc.UUCP and let other sites figure it out. Sometimes that
- works, sometimes it doesn't. If the site doing the routing understands that
- .UUCP is a fake domain, governed by the USENET maps and not by the normal
- Domain Name Service lookup servers, it works. Others just give up and throw
- the message away.
- If you ask your neighbors what they can handle, and try to coordinate things,
- you can usually works around these problems. Just don't expect the solution
- that works for you and your neighbors to work everywhere.
-
-
- The Commonly Asked Questions
-
-
- It's time to give some quick answers to the most common questions I am asked.
- 1. Whats the phone number for USENET? Oh boy, my favorite! Please see the
- paragraphs above about how to find a site to connect with. There is no single
- phone number, but many options on how you can connect. Some very inexpensive
- or free, some costly.
- 2. How do I join the Internet? Most sites don't have to. Usually Network News
- and USENET access is sufficient. But if you really need direct access to many
- other computers for sharing information, and have the money to support the
- connection (varies by region, but usually not less than $1,000/month with
- installation charges upwards of $10,000), there are regional networks in each
- part of the country that you would connect to. Send me mail if you are
- interested and I can try to find out the regional contact in your area.
- 3. Where is the nearest archive site? This is a hard question, partially
- because near is relative, and partially because it varies based on what
- newsgroup is desired. Three popular archive sites are osu-cis (a UUCP
- reachable archive site listed in the maps), Portal, and UUNET. Ohio State is a
- free archive; Portal and UUNET charge for access.
- 4. Can you send me a posting you described in a prior column? Usually I can't.
- My site is not an archive site. After I write about a posting, it gets deleted
- (unless I keep it to use it on one or more of our computers). By the time the
- column appears, the posting is long deleted. So even if I could make floppies
- or tapes for you, the original file is gone. All I can do is refer you to an
- archive site.
- 5. How can I find the electronic mail address for a user at some site? The
- best way is to call that person and ask for his or her electronic mail
- address. If you post a request to the network, it gets sent to each of several
- hundred thousand computers, costing many thousands of dollars of everyone's
- money. Make a simple phone call and ask.
- Lastly, remember that Network News is not a BBS. If you post something to a
- newsgroup, it is broadcast to hundreds of thousands of computers all over the
- world. Some of the readers will not have English as their native language,
- even though most postings are in English. Therefore, think twice before you
- write something. Check your facts, try not to inflame others, and remember
- that the comment that you took personally may only be the author's problem in
- phrasing a reply in a short, written English message. USENET Network News is a
- cooperative venture, and we all need to cooperate for it to work.
- There will always be those that abuse available resources, and those that are
- quick to reply, insulting others. However, if we try to ignore them, the
- bandwidth saved may let USENET and Network News survive another year. Good
- luck finding a connection to USENET. When you do, say hello. Most of us are
- more than willing to help.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Questions & Answers
-
-
- YACC And Lex
-
-
-
-
- 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) 493-4390. When you hear the answering message,
- press the * button on your telephone. Ken also receives email at
- kpugh@dukemvs.ac.duke.edu (Internet).
-
-
- Q
- What are YACC and Lex? I am currently developing a mapping package that reads
- xyz values, posts them on a map grid, contours them, and does various other
- manipulations with the data. It turned out to be that the number of options
- you have with the mapping part is enormous, e.g. title blocks, which pen to
- use, linear grid or geographic, etc...
- I am using with all my programs the getopt function for command line options
- processing. I don't think setting all the available possible options from the
- command line would be practical.
- I am looking for a utility that would allow me to create my own macro language
- with specific keywords that accepts options, sort of like many getopts. This
- would obviously call for another utility to parse my macros.
- Will YACC and Lex help? If you think they will, I would appreciate a quick
- review on what they are and how they can solve my problems. If they won't, can
- you please suggest something else.
- Sami Kurdy
- United Arab Emirates
- A
- Lex can help you with your problem. What you want to do is to recognize
- different strings of characters that satisfy certain rules.
- The Lex (LEXical analyzer) program produces a C source file that looks for
- specified sequences of characters and produces actions when it finds the
- sequences. The output of Lex is compiled into your C program. The resultant
- program may be longer and slower than one you could write, but it will take
- you less time to develop it.
- Let me summarize some of the features of Lex. You specify any number of
- regular expressions. For example, abcd matches the string abcd. [abcd] matches
- a single character that is a or b or c or d. [abcd]* matches a string
- containing any combination of a, b, c, and d, such as abcd, bbcda, or
- aabbbbcccc. [a-zA-Z]* matches any string containing upper or lower case
- letters, such as ABCabcd.
- For each regular expression, you specify a C code fragment to be executed when
- the expression is found. For example:
- [abcd] printf("Found a
- string of abcd");
- To resolve problems in recognizing strings, Lex uses the longest string
- possible that matches. If there are still ambiguities, it executes the first
- rule that matches.
- Lex creates a C routine called yylex( ). When you call this routine from your
- program, it reads the input for characters. When it recognizes a specified
- character sequences, it performs the matching action, if any. For example, if
- you were using it to recognize keywords, you could have it return the
- corresponding numeric token values, which you could then process. You might
- specify this as:
- #define IDENTIFIER 10
- #define PLUS 9
- #define INTEGER_NUMBER 11
- #define SET_COMMAND 12
-
- [_a-zA-Z][_a-zA-Z0-9]* return IDENTIFIER;
- '+' return PLUS;
- [0-9]* return INTEGER_NUMBER;
- SET return SET_COMMAND;
- Lex may be sufficient for your needs, if the command language consists of a
- number of commands with optional parameters following. The string that Lex has
- matched is kept in an array called yytext[], so you could call sscanf to do
- conversion of numeric values that were found. If you used the above values,
- you might write a code fragment that looked like the code in Listing 1.
- This would look for the word SET followed by an integer number. If an integer
- number did not follow SET, then a message would be generated and the program
- would exit. With a simple command language, using Lex to analyze the input for
- commands would decrease the amount of your coding.
- From the above, you see that Lex can be used to generate tokens. YACC (Yet
- Another Compiler Compiler) can use these tokens with specified rules to
- produce additional actions. You could write your own compiler, if you wish,
- using YACC to do the parsing.
- The YACC set of rules can be more complex than the Lex rules, since one rule
- can depend on the results of other rules. The rules can specify a grammar,
- rather than simple pattern matching. The general form of YACC input is:
- declarations
- %%
- rules
- %%
- subroutines
- The rules look somewhat like the Backus-Naur Form (BNF) for a programming
- language. For example, the BNF rules for C can be found in the ANSI C
- specification or in Appendix D of All on C. Using the above Lex tokens, you
- might make up YACC specifications as:
- %token IDENTIFIER
- %token PLUS
- %token INTEGER_NUMBER
- %token SET_COMMAND
- %%
- ADD_EXPRESSION : IDENTIFIER PLUS IDENTIFIER
- { printf("Add expression")} ;
- SET_EXPRESSION : SET_COMMAND INTEGER_NUMBER
-
- %%
- yylex() /* Generated by lex or your own */
- ;
- Developing a language (with or without YACC) is a bit complicated. However, if
- your command language involves computing expressions or control loops, using
- YACC is simpler than trying to write your own parser.
- Language construction is a bit beyond the scope of this column. Perhaps Robert
- or P.J. can get an article written about that topic. (KP)
- Q
- I am trying to develop a TSR screen saving routine, but I am running into
- problems. I have an IBM compatible, 80286 AT computer with VGA graphics. I
- would like to be able to save a screen from the DAK soul snatcher program VID.
- EXE. Can you offer a solution in Turbo C++?
- Also, what is a debugger (like Turbo Debugger), and what is it used for?
- Matt Nowicki
- Millersville, MD
- A
- There are many aspects to TSR programs, which would greatly exceed the space
- in this column. I suggest you obtain one of the libraries of TSR functions
- (e.g. Blaise Tools), or check the past articles in The C Users Journal or
- Computer Language for how to create TSRs.
- A debugger allows you to step through your C program, either by one line at a
- time or by blocks of code. At each step, you can examine the values of
- variables. You can set a breakpoint in the code, which will allow you to
- execute all the code up until that point and then permit you to go into a
- single step mode. A debugger is an excellent way to learn how C works, as well
- as a way of checking out small routines or programs.
- Debuggers vary in their features. For example, most debuggers permit you to
- set breakpoints on a particular line of code. Whenever your program reaches
- that line, a break will occur and you need to type a key to resume execution.
- Other debuggers allow you to specify an expression (such as "i = = 50"), which
- must be true for the break to occur. This can eliminate a lot of keystrokes.
- (KP)
-
-
- qsort Comparison Function
-
-
- Two months ago, Firdaus Irani of Chestnut Hill, MA wrote about an error he got
- when trying to compile the program in Listing 2. The error read:
- Turbo C++ Version 1.00 Copyright (c) 1990 Borland
- International q_sort.c:
- Error q_sort.c 12: Type mismatch in parameter
- '__fcmp' in call to 'qsort' in function main
- *** 1 errors in Compile ***
- I checked with Robert Jervis, who is another member of the ANSI C committee.
- As I expected, the error is due to a function prototype nested in a prototype.
- This is the expected behavior, and it results from how function pointers in
- function prototypes are evaluated.
- For simple function parameters, such as ints and doubles or data pointers, if
- the actual parameter is assignment compatible with the formal parameter, the
- conversion takes place silently. A parameter which is a pointer to a function
- has a type that includes the types of its parameters. For example, for a
- function prototyped as
- qsort(void *array, int element_size,
- int element_count, int _fcmp(void *, void *));
- ***** which is first, size or
- you can call this with:
- char char_array[10];
- int compare_function(void *, void *);
-
- qsort(char_array, 10.1, (char) 1, compare_function);
- The compiler will ensure that the parameters are initialized as:
- array = char_array
- element_count = 10/* Converted and rounded to 10 */
- element_size = 1 /* Increased to a int */
- _fcmp = compare_function
- However, you cannot call it with:
- int compare_function(char *, char *);
- since the types which the function expects differ (even though they are
- assignment compatible).
- Note this means that if you attempt to use strcmp( ) as a parameter to qsort(
- ) and you include <string.h>, you will get this parameter mismatch error.
- You could change the prototype definition to read:
- int _fcmp( )
- This way, it will accept a pointer to any function, regardless of type or
- number of parameters. (This is ANSI, but disparaged.) (KP)
-
-
- Readers' Replies
-
-
-
-
- External Name Headers
-
-
- I am a Staff Systems Software Engineer at Seagate Technologies. Several
- engineers in my department read the journals, and find The C Users Journal to
- be one of the best.
- I read the question by Andreas Lang in the October 1990 issue of The C Users
- Journal regarding keeping external variables in one .h file. I found a
- technique in Dr. Dobb's Journal, curiously the same month of October 1990, in
- a letter by Robert White. Following is a memo I wrote on this technique, using
- it to solve the problem M. Lang wrote about:
-
- I ran across a letter in the October 90 The C Users Journal asking how
- variable declarations and the extern declarations of those variables could be
- kept in sync, especially when you want to initialize the variable at compile
- time.
- The whole point of the technique is to declare all the information you need in
- all cases in a macro call, then define the proper macro to extract those
- parameters (pieces of information) that you want for the specific case and put
- them in the desired format.
- For the case of variable declaration, see Listing 3. You select one of the
- sets of macros via a defined symbol or its lack of a definition as shown in
- Listing 4. This would generate two different cases, which are shown in Listing
- 5. This is the cleanest solution I have found yet to this problem.
- Don bandit Gangwere
- Scotts Valley, CA
- The letter that Don referred to in Doctor Dobb's Journal used a similar
- technique to keep enum values and corresponding strings straight. See Listing
- 6.
- In an include file, say table.h, are the values:
- TABLE_VALUE(sunday, "SUNDAY"),
- TABLE_VALUE(monday, "MONDAY"),
- TABLE_VALUE(tuesday, "TUESDAY"),
- To use these values, you could use the declarations shown in Listing 7.
- This seems like a good idea to keep enums and strings in sync. (KP)
- A response in your October Q?/A! column in The C Users Journal told Andreas
- Lang that there was no solution to having one .h file serve for both
- definition and externals declaration. It isn't especially elegant, but the
- code in Listing 8 works for me.
- This isn't original with me, but I can't remember to whom to attribute it.
- Terry Shankland
- Mesa, AZ
- The one specific comment I would make on your coding is to avoid declaring a
- structure template and a variable at the same time. For example, it ought to
- look like:
- struct range
- {
- int xmin, xmax, ymin, ymax;
- };
-
- GLOBAL
- struct range data_range
- #ifdef ALLOCATE_SPACE
- = { 0, 0, 0, 0 }
- #endif
- ;
- The next letter mentions my preference for a separate header file. Either that
- or duplicating the defintion just reads better to me. For example, to keep
- everything in the same source file, I might write it as shown in Listing 9. I
- made the 0 value explicit for variable2, just for consistency's sake.
- I find the fewer #ifdefs that I have to deal with, the easier it is for me to
- understand. (KP)
- I'm responding to the letter from Andreas Lang in the October issue of The C
- Users Journal. Lang writes: "I am trying to keep all external variables in
- just one .H file." He offers his solution but notes, "it isn't very elegant."
- Lang's goal and his problem draw attention to organization, the heart of the C
- language. In C, the question is always, "How shall I organize my code and my
- variables for maximum readability and productivity?" At this point in my own C
- career, I usually can produce the code that I need rapidly; but I spend a
- great deal of time reorganizing it. My own struggle with C is less with the
- syntax than with the underlying concepts of organization that are built into
- the language. I still struggle with this. I have made my own efforts in the
- direction that Lang is giving, with success and frustrations similar to his. I
- no longer believe that the organizational goal expressed by Andreas Lang is
- suited to the nature of C.
- The nature of C dictates that you minimize the number of external variables. I
- have determined this by trial and error. Given that code reusability is a key
- reason for working in C, one strives to organize in such a way that code
- re-use is simplified. I try to move new code rapidly into a temporary library.
- First of all, I must organize the source in a certain way so that it is suited
- to being in a library. Then, creating the library produces an ASCII file list
- of public symbols in the library (the .LST file produced by TurboC is an
- example). This ASCII file is an important reference tool. It should be a
- concise reference to the functions and external variables in the library-ized
- source. It shouldn't be littered with names that could have been eliminated by
- reorganization.
- The nature of C, I believe, is not suited to collecting all external variables
- in a single header file. Rather, the nature of C dictates that you minimize
- the number of external variables that you use. Often a variable need be shared
- by only a few functions. I take this as a signal that those functions belong
- together in a common source file. If this cannot be done, the variable can
- probably be passed as a parameter, rather than an external. When this cannot
- be done, I want the declaration of an external variable together with the code
- that requires the external. The reference serves as a warning notice to myself
- to be careful.
- Sometimes an entire set of variables must be shared by a number of functions.
- This is my clue to create a structure to house that set of variables. Not only
- does this reduce the number of variables to one, it also solidifies concepts
- forming in my code. The name which seems reasonable to apply to a new
- structure often identifies the concept. Thus I find the organizational demands
- of C help me to formulate my ideas.
- The apparent redundancy of variable and function declarations used to bother
- me. Now they hardly seem redundant at all. It used to bother me that function
- declarations were a necessary prerequisite to function calls. Now I view the
- function declarations as a valuable portion of my code. They summarize
- important information in a convenient place for handy lookup.
- It used to bother me that variables must be declared before they can be used.
- Now I think of the variable declarations as an organizational tool. Now, the
- undeclared variables of GWBASIC make me uncomfortable. BASIC provides no
- built-in method of assuring that I'll be able to go back to a certain spot in
- my code and double check the name of a variable, or to see what names have
- been used.
- The thing that bothers me now about C is that I find little discussion that
- addresses the nature of C. One has to hunt everywhere, read everything, and
- glean what one can. Many analyses offer solutions at the syntax level, rather
- than emphasizing the underlying concepts of organization which are built into
- the language.
- In your reply to Lang, you write, "There is no write-once solution... You need
- to repeat the prototype." This cold, impersonal rule of thumb states the
- facts. But the wisdom in your personal coding habits does reflect the nature
- of C: "I don't try to initialize in a header file, but I do include a copy of
- the header file in the source... The compiler should check for agreement
- [between the extern reference and the definition]."
- "The compiler should check for agreement." This solves a problem that I have
- run across, and never managed to solve. Including a copy of the header in the
- source was something that, until now, seemed to me unnecessary and redundant.
- Now I see it as necessary and unredundant. Thanks.
- To me, your remarks on your personal coding habits address the nature of C.
- Perhaps you view personal coding habits as a subjective issue. I would
- disagree with that. Obviously, some aspects are personal. Generally, I think
- the things that I try are personal. But when I've been forced by the language
- to modify my coding habits, I know I've bumped up against the nature of C. And
- when I finally make the right guess, and things start to work, I know that I
- have an objective solution. I'd like to see more emphasis placed on this thing
- that I call the nature of C.
- Aside and apart from all of the above, let me say that I really enjoy your
- column. The Q&A format is always instructive. Also, you're very brave to
- publicly offer solutions to spur-of-the-moment problems. And you're very
- honest to print replies from readers who have a month or more to come up with
- solutions that may be even better than yours! But seriously, I keep all my
- back issues of CUJ on my desk, and I always refer to your column.
- Art Shipman
- New York, NY
- Thanks for your ideas. It makes explicit for me what I have been espousing
- implicitly. I agree that there is a nature to C, as you so well explain. I
- don't like to make dogmatic rules, but rather to make suggestions as to its
- nature.
- For example, if one finds themselves using external variables to communicate
- between source files, one should check the organization of the code. If you
- find yourself ever doing a cut and paste of a set of code, you should examine
- why and whether a general routine can be created. If you go beyond a single
- member operator (e.g. employee.hire_date.month), you should examine the
- modularity of your code.
- What you've described in your letter is part of the object-oriented
- design/modular programming concept that is also part of the essence of C. I
- have been an advocate of structures, even before structure assignment was made
- part of the language. Using structures and developing functions to operate on
- them is the greatest step one can take to object-oriented programming. As I
- described in my talk at the C Forum in October, if you can create these
- structures and functions, you can have many of the benefits of object-oriented
- design, such as code reusability, without learning a new language. (KP)
-
- Listing 1
- ret = yylex();
- switch(ret)
- {
- case SET:
- /* Next input should be the numeric value */
- ret = yylex();
- if (ret != INTEGER_NUMBER)
- {
- printf("\n ERROR -- number not specified
-
- after SET");
- exit(0);
- }
- else
- sscanf(yytext,"%d",&value);
- break;
- ...
-
-
- Listing 2
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
-
- int comp(unsigned char **, unsigned char **);
- unsigned char *list[] = { "cat", "car", "cab",
- "cap", "can" };
-
- main()
- {
- int x;
-
- qsort(list, 5, sizeof(unsigned char *), comp);
- for (x = 0; x < 5; x++)
- printf("%s\n", list[x]);
- return 0;
- }
-
- int comp(unsigned char **a, unsigned char **b)
- {
- return strcmp(*a, *b);
- }
-
-
- Listing 3
- VAR( int, foo ); /* simple variable */
- INIT( WORD, bar, 5 ); /* init variable */
- INIT( BYTE, aaa[5], { 1,2,3,4,5});
- /* init array */
- TBL( BYTE, tbl[], table.h); /* init table */
-
-
- Listing 4
- #ifdef MAIN_FILE
- #define VAR( ttt, vvv) ttt vvv
- #define INIT( ttt, vvv, iii ) ttt vvv = iii
- #define TBL( ttt, vvv, iii ) ttt vvv = { #include iii }
- #else
- #define VAR( ttt, vvv ) extern ttt vvv
- #define INIT( ttt, vvv, iii) extern ttt vvv
- #define TBL( ttt, vvv, iii) extern ttt vvv
- #endif
-
-
- Listing 5
- MAIN_FILE defined
-
- int foo;
- WORD bar = 5;
-
- BYTE aaa[5] = {1,2,3,4,5};
- BYTE tbl[] = { #include table.h };
-
- MAIN_FILE not defined
-
- extern int foo;
- extern WORD bar;
- extern BYTE aaa[5];
- extern BYTE tbl[];
-
-
- Listing 6
- #ifdef MAKE_STRING_TABLE
- #define TABLE_VALUE(value, string) string
- #else
- #define TABLE_VALUE(value, string) value
- #endif
-
-
- Listing 7
- #define MAKE_STRING_TABLE
- char *day_strings[] =
- {
- #include "table.h"
- };
-
- #undef MAKE_STRING_TABLE
- enum e_day =
- {
- #include "table.h"
- };
-
-
- Listing 8
- /*** IN THE FILE GLOBALS.H ***/
-
- #ifdef ALLOCATE_SPACE /* allocate space for globals */
- #define GLOBAL
- #define INIT(x) x
- #else /* just declare externals */
- #define GLOBAL extern
- #defint INIT(x)
- #endif
-
- /*
- NOTE: The INIT(x) macro won't work with aggregates
- because it interprets a comma as indicating a new
- macro parameter, not as part of the current
- parameter. Aggregates are initialized with
- #ifdef ALLOCATE_SPACE
- */
-
- GLOBAL
- int variable1 INIT(=1),
- variable2;
-
- GLOBAL
- int variable1 INIT(=1),
- variable2;
-
-
- GLOBAL
- struct range
- {
- int xmin, xmax, ymin, ymax;
- } data_range
- #ifdef ALLOCATE_SPACE
- = { 0, 0, 0, 0, 0 }
- #endif
- ;
-
- /** IN THE MAIN .C FILE ***/
-
- #define ALLOCATE_SPACE
- #include "globals.h"
-
- /* So, our inclusion becomes: */
-
- int variable1 = 1,
- variable2;
-
- struct range
- {
- int xmin, xmax,; ymin, ymax;
- } data_range = { 0, 0, 0, 0 };
-
- /* IN ALL OTHER .C FILES */
-
- #include "globals.h"
-
- /* So our inclusion becomes: */
-
- extern
- int variable1,
- variable2;
-
- extern
- struct range
- {
- int xmin, xmax, ymin, ymax;
- } data_range;
-
-
- Listing 9
- struct range
- {
- int xmin; /* Comment on xmin */
- int xmax; /* Etc. */
- int ymin;
- int ymax;
- };
-
- /* These are the references */
-
- /* Comment on data_range */
- extern struct range data_range;
- /* Comment on variable1 */
- extern int variable1;
- /* Comment on variable2 */
-
- extern int variable2;
-
- /* These are the definitions */
-
- #ifdef ALLOCATE_SPACE
- struct range data_range = { 0, 0, 0, 0 };
- int variable1 = 1;
- int variable2 = 0;
- #endif
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Object-Oriented Design with Applications
-
-
- Alex Lane
-
-
- Alex Lane is the Senior Technical Writer in the Languages Business Unit at
- Borland International. This article was excerpted from Turbo C++ by Example
- (M&T Books, 1990) and has been reprinted with the permission of M&T Books
- (415/366-3600).
-
-
- The object model is taking the programming world by storm, to the extent that
- the technology for implementing object-oriented systems is outstripping the
- ability of many programmers to use it effectively. Using a new technology
- effectively involves adopting new design techniques, not simply using new
- tools. Grady Booch's Object-Oriented Design with Applications is a practical
- guide to constructing object-oriented systems.
- The book's emphasis is on the pragmatic, for although Booch does discuss the
- fundamental concepts of the object model and introduces the reader to a
- notation system for object-oriented design, most of the book focuses on
- nuts-and-bolts discussions of object-oriented system design. Concepts, the
- method, and applications are, in fact, the three major sections of the book.
- The audience for the book encompasses both computer professional -- including
- software engineer, analyst, and manager -- and student.
-
-
- Concepts
-
-
- The discussion of concepts starts with an examination of complexity. Booch
- tackles the issue of what makes software complex and links complexity directly
- to the "software crisis." He lists five attributes of complex systems and
- discusses the role of both algorithmic and object-oriented decomposition to
- help cope with this complexity.
- Booch then embarks on a definition of the object model that discusses its
- evolution, its elements, and its applications. This model is a "language-free"
- model, in that it is not predicated on any one language, but rather abstracts
- the model from existing concepts. Booch then discusses what is and is not an
- object and a class, discusses the relationships among objects, among classes,
- and between objects and classes. The first section ends with an important
- chapter on classification.
-
-
- The Method
-
-
- The section on the method begins with a justification for and presentation of
- the object-oriented design notation that Booch uses in the rest of the book.
- This includes notation for class and object diagrams (for documenting the
- logical design of a system), and module and process diagrams (for documenting
- the physical design). Booch also provides state transition and timing diagrams
- to show the state space of a class instance and dynamic object interactions,
- respectively. On my first read of the book, I skipped these sections, which
- was a mistake, since they are used extensively in the applications
- discussions. The notation is, however, reproduced on the book's inside covers
- for easy reference.
- The book continues its presentation of the method by describing the design
- process ("round-trip gestalt design"), and discussing the pragmatics of design
- given the incremental, iterative nature of object-oriented design.
-
-
- Applications
-
-
- The first two sections are a preamble of sorts to the third section covering
- applications. While I found this section to be a real treasure -- Booch takes
- five applications from the analysis stage through design, evolution, and
- modification -- readers who specialize in only one object-oriented language
- will likely leave 80 percent of this section unread because the author
- implements each of the five applications in a different language. A home
- heating system is done in Smalltalk. A geometric optics construction kit is
- implemented in Object Pascal. C++ is used to construct a problem reporting
- system. Common LISP is applied to the problem of cryptanalysis. Finally, Ada
- is used to build a traffic management system.
- In today's computing world, developers have increasingly become cliquish,
- sticking to their own specialty and shunning others. However, as Robert
- Heinlein once wrote, "Specialization is for insects," and the reader who
- ignores the "other" parts of this section on applications is shortchanging
- him- or herself. You don't have to be a guru at any of these languages to get
- a great deal out of this section.
-
-
- In General
-
-
- Many computer science books -- particularly those undertaking to explain
- design -- have the unfortunate characteristic of taking themselves too
- seriously. This is certainly not the case in Booch's book. Illustrative
- cartoons scattered throughout the book will bring a smile to your lips as they
- pound home their message. Booch also has a refreshing way of expressing ideas.
- Nobody, he notes, would expect to successfully (or cheaply) add a sub-basement
- to a 100-story building, yet equivalent changes are demanded in software all
- the time. I'll marshall that argument the next time I'm confronted with such a
- change.
- The book makes good use of typography, with distinguishable fonts for text and
- code, and recognizable section headers. There is a band of whitespace along
- the sides of the page, and the pages stand up well to scribbling and accenting
- with yellow marker. Figures, cartoons, and sidebars break the monotony of
- text-filled pages. Sidebars are noted in the Table of Contents.
- The end material in the book is helpful, and, in general, well organized. A
- capsule summary of object concepts and the characterstics of object-oriented
- languages is presented in the appendix. These are, of necessity, pretty sparse
- summaries, and Booch provides a list of references for each language. The
- appendix is followed by the book's footnotes (I would have preferred them at
- the end of each chapter), and a good glossary of terms.
- The bibliography is arranged in a classified manner. References to
- object-oriented design are grouped separately from those on object-oriented
- programming, software engineering, etc. This gives the bibliography a limited
- value. If you know your subject and want a list of books, you're fine. If you
- know only your author, you may have to look in several different
- classifications before finding what you seek.
- The field of object-oriented design is young, and there is always the risk
- that the first few books published on the subject will be rushed and
- incomplete. Not so here. Grady Booch's book thoroughly covers its material in
- an engaging manner, and is destined to be a classic in the field. It deserves
- a place on the shelf of every serious software developer.
- Object-Oriented Design with Applications
- Grady Booch
- Benjamin/Cummings
- Publishing Co.
- ISBN 0-8053-0091-0
- 580 pp.
- (1991)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Algorithmics: The Spirit of Computing
-
-
- Tom Rombouts
-
-
- Tom Rombouts works in product development for Ashton-Tate in Torrance,
- California. He is also the leader of the dBASE SIG of the Los Angeles Computer
- Society. USENET: tomr@ashtate.A-T.com.
-
-
- Algorithmics is an introduction and overview of the history, methods, theory,
- and future of algorithm study and analysis. Excellently written,
- well-researched, and with numerous illustrations, the book contains
- fascinating material in every chapter. However, David Harel's approach may be
- too theoretical for most day-to-day programming work, and likely too academic
- and mathematical at times for many managers or end-users.
- To some, the fundamental issue of computer science might be creating faster,
- smaller, and cheaper hardware. To others, it might be designing more efficient
- and reliable methods of software development. But to noted theoretician David
- Harel, it is Algorithmics (the study of algorithms -- planned processes used
- to solve specific problems) that most captures the true "Spirit of Computing."
- Based on a series of radio lectures by the author, the book starts by
- comparing a recipe from a cookbook to a computer program, and eventually works
- its way up to presenting issues on the leading edge of algorithm theory
- involving such things as algebraic quadratic equations, prime number theory,
- and reduction equivalency. Believe it or not, in some ways factoring a
- 150-digit number is not so different from preparing a chocolate mousse!
- The book consists of four somewhat self-contained parts. Part one,
- Preliminaries, includes a quick historical review, a summary of the basic
- concepts of algorithms and data, and a brief survey of common computer
- languages and the types of problems they are best suited for.
- The second part, Methods and Analysis, covers algorithmic methods, trying to
- prove the correctness of algorithms, and efficiency considerations regarding
- algorithms.
- The third part, Limitations and Robustness, covers topics such as the two
- classes of problems that computers can solve, problems that can not be solved
- by computers, and finite state machine theory. This section contains the bulk
- of the book's math and theory, which, depending on your point of view, is
- either the most or least important material.
- Part four, Relaxing the Rules, discusses issues such as new problems that must
- be addressed when introducing parallel processing, algorithms that use
- randomization techniques to generate "probably correct" solutions, and
- artificial intelligence and possible limits of future computing applications.
- Finally, there is a 48-page section of bibliographic notes and a comprehensive
- 20-page index.
- Although the author intends the book to be read sequentially, two chapters in
- particular can be read by themselves. Programming Languages gives a quick yet
- insightful overview of seven major languages (including Pascal and not C?),
- while Algorithmics and Intelligence gives a clear outline of issues relating
- to artificial intelligence in areas such as voice and pattern recognition and
- game theory.
- I find one of the book's real strengths to be its light, humorous style. Each
- chapter begins and ends with old testament bible quotations that apply (out of
- context, of course) to the topics covered in that chapter. Or, in the
- bibliography for the section on recursion, the book references itself!
- The book teaches that there are four main categories of algorithmic problems,
- only one of which can be realistically solved by computers. Given a certain
- minimal set of abilities, and unlimited space and time, all programming
- languages and computers are equivalent in the types of problems they can and
- cannot solve.
- Overall, I think this is an excellent book. However, it probably will not help
- you debug your C program, nor help you decide when to upgrade to the latest
- version of your favorite compiler.
- On the other hand, it is my (perhaps minority) opinion that knowing more than
- the absolute minimum needed to get your paycheck can make many day-to-day
- tasks seem much simpler in comparison. After all, if you can grasp the
- concepts of the limits of algorithmics, debugging a text parser should be a
- breeze!
- Reading the book might even enhance your humor skills at your next party. The
- next time someone says, "Did you hear the one about the traveling salesman?"
- you will be able to immediately reply: "Yes! The traveling salesman's search
- for a minimal, perhaps Hamiltonian, visitation path is perhaps the best-known
- example of a Nondeterministic Polynomial-time Complete algorithmic problem!"
- Algorithmics: The Spirit of Computing
- David Harel
- Addision-Wesley
- 1987
- $25.00
- 425 pages
- ISBN 0-201-19240-3.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Editor's Forum
- It is a truism that we live in an era of electronic communications.
- Nonetheless, I find myself continually astonished at how pervasive the
- electronic grapevine has become.
- Perhaps it is because I am basically conservative. Electronic mail has long
- come free with UNIX. Even so, I resisted the use of e-mail within Whitesmiths
- all the years we were tied into the UNIX network. Mail within the company was
- useful, I felt. But I saw too many hours wasted by people generating, and
- wading through, gossip on the various forums.
- When I went on my own, I quickly noticed the vacuum that comes with being
- disconnected from the technical community. I gave in, bought the UUMAIL
- package from Vortex Technology for my PC, and signed up with UUNET to get on
- the network. My usage has grown exponentially. I once stayed comfortably
- within the 60 minutes per month that came with my rock bottom base rate. Now I
- blow that quota by shipping a single book manuscript, or a batch of articles
- for CUJ. It is a rare week that I don't get mail from four continents.
- I tell you this for several reasons. I am beginning to get letters to the
- editor via e-mail. That is a milestone of sorts, at least for me, and a true
- sign of the times. It is only fair that I give in a further step and begin
- advertising my e-mail address on a wider basis. It will stay unchanged even
- for the year I am spending in Australia, thanks to automatic mail forwarding.
- Were it not for e-mail and FAX, of course, I couldn't possibly edit a magazine
- this year that is published in faraway Kansas.
- I welcome this growing electronic interconnectivity, albeit reluctantly at
- times. But I still can't bring myself to read comp.std.c, the electronic forum
- on standardizing C.
- P.J. Plauger
- pjp@plauger.uunet.com
- P.S. I invite our readers to stop by the R&D booths at SD '91 in Santa Clara
- (February 12-15). Both CUJ and TECH Spec will be exhibiting, so stop by and
- say hello to our staff. We'd like to hear what you have to say.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- New Products
-
-
- Industry-Related News & Announcements
-
-
-
-
- C++ Solution For Embedded Systems
-
-
- Microtec Research has introduced C++ development tools for embedded systems
- engineering. The tools include an optimizing C++/ANSI C cross compiler, a
- C++-compatible source-level debugger, and C++ symbol name inspection tools.
- The new product, CCC68K, implements the 2.1 definition for the C++ language
- with numerous extensions to support embedded applications development.
- Complementing the compiler is the XRAY68K debugger for debugging optimized C++
- code. Microtec Research also supplies a variety of inspection and conversion
- tools. First targeted for the Motorola 68000 family of microprocessors, CCC68K
- supports a superset of the v2.1 C++ language definition.
- The CCC68K package also includes the ASM68K relocatable macro assembler and
- single and multiple inheritance runtime class libraries for I/O streams and
- complex arithmetic. The debugger provides a window-oriented user interface
- that separates user code and debug information into appropriate groupings.
- Prices for the complete toolkit begin at $6,600. For more information, contact
- Microtec Research, 2350 Mission College Blvd., Santa Clara, CA 95054, (408)
- 980-1300; FAX (408) 982-8266.
-
-
- Mark Williams Ships Coherent 3.1
-
-
- Mark Williams Company is now shipping Coherent v3.1, an upgrade of its
- UNIX-compatible operating system. It offers a C compiler, lex, yacc, make,
- UUCP, text processing, program development, administrative and maintenance
- commands.
- Coherent v3.1 includes information on accessing the MWC UUCP bulletin board
- system, a configurable AT hard disk driver for unsupported disks, an Adaptec
- AHA-154x series SCSI device driver, COM3 and COM4 serial line support, and a
- RAM-disk device driver.
- Coherent v3.1 also offers a generic multi-port serial card device driver, a
- miscellaneous function library with source code, 16-bit version of compress,
- uncompress and zcat, enhanced mail, man, scat and msgs utilities, new and
- updated on-line manual pages, and numerous bug fixes.
- Coherent v3.1 sells for $99.95. For more information, contact Mark Williams
- Company, 60 Revere Drive, Northbrook, IL 60062, (708) 291-6700; FAX (708)
- 291-6750.
-
-
- Intek C++ For Windows
-
-
- Intek Integration Technologies is now shipping v2.0b of its C++ product. Intek
- C++ is a translator that generates C code as output. The DOS version runs in
- protected mode at compile time, allowing access to all memory in the machine.
- Intek C++ supports small data models (small and medium memory) as recommended
- by Microsoft. Intek C++ v2.0b supports the extended keywords near, far, huge,
- cdedl, pascal, fortran, as well as the new Microsoft v6.0 keywords, _based,
- _self, _segment, and _segname.
- Intek C++ supports a variety of C compilers, including Microsoft C v5.1 and
- v6.0; Microsoft C+ with Windows, v5.1 and v6.0; Turbo C v2.0; Watcom C v8.0;
- Watcom C 386 v8.0; and Metaware High C 386. Intek C++ can work with other
- standard and robust C compilers that support 31 character names. Some of the
- users cross-compile from Intek C++ to C compilers for chips like the TI 34010.
- Intek C++ sells for $495 for single copies; site licenses are available. It
- runs under MS-DOS on the 80386 with 2Mb of RAM, and also under UNIX System
- V/386. The UNIX version can cross-compile to any DOS C compiler.
- For more information, contact Intek Integration Technologies, 1400 112th Ave.
- SE, Suite
-
-
- Saber Software Offers C++ Environment
-
-
- Saber Software has introduced Saber-C++, a workstation-based UNIX C++ program.
- It is a dual C++ and C language development environment.
- Software engineers can use C++ immediately because Saber-C++ offers an
- interactive workspace and a full C++ interpreter for prototyping,
- experimentation and runtime error checking. Users are able to view and
- understand program elements ranging from simple data structures to complex C++
- class hierarchies.
- Saber-C++ offerings include automatic static and runtime error detection,
- source-level debugger, graphical browsers, interactive workspace, incremental
- linker, integration with UNIX tools, and extensive documentation.
- Saber-C++ sells for $3,995 and includes technical support and maintenance for
- one year. For more information, contact Saber Software, 185 Alewife Brook
- Parkway, Cambridge, MA 02138, (617) 876-7636; FAX (617) 547-9011.
-
-
- C++ for UNIX 80386/486
-
-
- Computer Innovations is now offering C++ v2.1 for UNIX System-V based
- 80386/486 systems, including those from Interactive, SCO, and ESIX.
- C++ v2.1 contains both Super-C and object-oriented features of C++. Super-C
- extensions include default function arguments, in-line methods and functions,
- type-safe linkage, and call by reference or value. Object-oriented features of
- C++ include class/method organization, overloaded operators, inheritance,
- polymorphism, constuctors and destructors, and virtual functions.
- The package includes standard C++ class libraries for streams and complex
- arithmetic. It sells for $495. For more information, contact Computer
- Innovations, 980 Shrewsbury Ave., Tinton Falls, NJ 07724, (201) 542-5920.
-
-
- C Code Builder Kit
-
-
- Now available from Intel Corporation is the 386/486 C Code Builder Kit. This
- software development toolkit for 32-bit applications from a single vendor,
- provides developers with the single source tools necessary for 32-bit software
- development on Intel 386 and i486 microprocessor-based personal computers.
-
- The toolkit contains the Intel 32-bit compiler, 32-bit protected mode
- source-level debugger, linker, libraries, utilities, make facility and DOS
- extender.
- The toolkit sells for $695. For more information, contact Intel Corporation,
- P.O. Box 58130, 3065 Bowers Ave., Santa Clara, CA 95052-8130, (800) 548-4725.
-
-
- C++ Compiler for CPU Board Family
-
-
- Heurikon Corporation has released the Green Hills C++ compiler for its
- 68030-based Multibus 1, Multibus II, and VMEbus CPU boards. The compiler will
- initially run under the UNIX v3.0 operating system and eventually will be
- ported to real-time operating systems.
- The Green Hills compiler produces optimized 68030 object code directly from
- the C++ source code. The compiler (object code) costs $2,500 and is available
- for the HK68/M130, HK68/V30XE, and HK68/M230 CPU boards running UNIX v3.0. For
- more information, contact Heurikon, 8000 Excelsior Dr., Madison, WI 53717,
- (608) 831-0900; FAX (608) 831-4249.
-
-
- IDB Object Database
-
-
- The IDB object database from Persistent Data Systems is programmable in
- standard C and runs both on UNIX workstations and on the PC compatibles under
- DOS and Microsoft Windows 3.0.
- IDB is based on IDL, the interface description language. IDL is a technology
- developed to manage the complex data structures of compilers and other
- software development tools.
- IDB applications may be configured with or without an optional display manager
- and browser. Using the browser's built-in display, edit, and search
- operations, a developer can prototype a new application by describing its
- data. Applications that use IDB for display and data management are portable
- across the range of supported platforms.
- The price of a single license ranges from $2,500 to $6,000. For more
- information, contact Persistent Data Systems, 75 West Chapel Ridge Road,
- Pittsburgh, PA 15238, (412) 963-1843.
-
-
- ToolBook for OS/2
-
-
- Now available from Asymetrix Corporation is the ToolBook 1.0 for the OS/2
- operating system. ToolBook is a software construction set that allows users to
- build custom graphical applications without traditional programming languages.
- ToolBook is a set of application development tools, including a color drawing
- package and an object-oriented programming language called OpenScript. The
- tools feature hyper-navigation capabilities, animation, text formatting, and
- flat-file database creation.
- The retail version of ToolBook for OS/2 comes with several applications: one
- that reads and writes dBase files, a hypermedia tutorial, a business
- calculator, an animation primer, a quick tour of ToolBook features, and a
- collection of scripts, clip art, and page designs. The OS/2 ToolBook sells for
- $395.
- Also available from Asymetrix is the ToolBook Developer Program, which
- provides both development and marketing assistance, and a new ToolBook
- Developer Partnership, an option that gives developers unlimited access to a
- designated Asymetrix support engineer for more in-depth technical support. The
- ToolBook Developer Program is free with the purchase of the $450 Asymetrix
- Author's Resource Kit, a package of tools that helps developers build and
- distribute applications.
- For more information, call (800) 624-8999.
-
-
- XVision 3.1 Expands Network Support
-
-
- Graphic Software Systems has released XVision v3.1. Its new features include
- increased X client capabilities, a new Program Starter that creates icons for
- individual X clients, and additional network support.
- XVision is a Microsoft Windows application that turns PCs into X Servers.
- Users can cut and paste between the two environments without a DOS hot-key
- mechanism. XVision is compatible with MS-Windows 3.0.
- XVision v3.1 allows the user to run up to 16 X clients simultaneously. The
- Program Starter now accepts command-line parameters that allow users to create
- icons for specific X clients. By selecting these icons, individual X clients
- are automatically started.
- New network support includes Sun PC-NFS, Ungermann-Bass Net/One TCP/IP, and
- Wollongong WIN/TCP for DOS. XVision can be networked to a variety of X hosts
- simultaneously, including UNIX and VMS.
- XVision v3.1 is priced at $449. Current XVision users can purchase the upgrade
- for $150. For more information, contact Graphic Software Systems, 9590 SW
- Gemini Dr., Beaverton, OR 97005, (503) 641-2200; FAX (503) 643-8642.
-
-
- Spell Checking Engine For Clipper and C Applications
-
-
- Geller Software's Spell Checking Engine is a library for Clipper and C
- developers that adds spell checking to applications.
- This function library consists of object modules that link into Clipper or C
- applications, an English language dictionary with more than 100,000 words
- stored in a compressed format, and a maintenance program for adding or
- removing words from the main dictionary or for building specialized
- dictionaries.
- The Spell Checking Engine also works with the Paradox Engine, Force, CodeBase
- 4, and other programs compatible with Microsoft C. Future releases will also
- be compatible with Borland's Turbo C and the Watcom C compiler.
- The Spell Checking Engine allows developers to add spell checking and
- correction of any user input within a custom application. The system can also
- spell check a wide variety of file formats, including xbase data and memo
- files, spreadsheets, and graphics files.
- For more information, call Glenn Hart at (914) 357-2055.
-
-
- 32-Bit Protected-Mode Extended Graphics Library
-
-
- A 32-bit 386 protected-mode extended graphics library, libhpgl.lib v5.0, is
- now available from Gary R. Olhoeft. This library supports graphics direct to
- hardware for PC-compatible standard modes EGA/VGA/MCGA/8514, VESA/SVGA,
- Hercules Graphics Station Card GB1024, Truevision ATVista-4M and Wysiwyg
- hardcopy to HP-GL and PostScript devices.
- The library supports mixed vector plotting and raster imaging, graphics
- viewports, user unit scaling, rotatable and scalable labels, and more up to
- 1,024 x 768 with 8-, 16-, or 32-bit graphics cards. The library comes with
- full source code in Micro Way NDP C-386 and Phar Lap 386 ASM, extensive
- example code and manual, 30-day money back guarantee, free upgrades for one
- year, and has no royalties for distribution of compiled composite code.
- Libhpgl.lib sells for $300. For more information, contact Gary Olhoeft, P.O.
- Box 10870 Edgemont, Golden, CO 80401-0620, (303) 279-6345.
-
-
- c-tree Plus Improves Resource Management
-
-
-
- FairCom has released c-tree Plus, a new file management and data server
- product that enhances and extends the functionality of the c-tree file
- handler.
- Developers can use c-tree Plus to produce a variety of application types:
- single or multi-user; single or multiple machine environments; applications
- with small or large amounts of data; optimized response time for a single
- query or a large request to select, sort, and return a set of data.
- c-tree Plus features include expanded support for variable length and BCD data
- types and keys; file level alternate collating sequence support; dynamic space
- reclamation; high-speed hashed data and index caching; and improved native I/O
- system utilization.
- c-tree Plus sells for $595. Current c-tree users can upgrade to c-tree Plus
- for $200. For more information, contact FairCom, 4006 West Broadway, Columbia,
- MO 65203, (800) 234-8180; FAX (314) 445-9698.
-
-
- Microware Offers Multimedia Interface
-
-
- Microware Systems is now offering Rave (real-time audio/video environment)
- real-time multimedia development environment and user interface for PC
- compatibles.
- Using OS-9000/Rave, designers can configure realistic user interfaces and
- control panels by combining high-quality audio, computer-generated graphic
- images, captured live video and customizable menus in the same user interface.
- Rave combines audio, video, and computer-generated graphics, and it can
- develop complex interface without writing code. Rave facilitates design of
- realistic real-time man/machine interfaces and runs on top of OS-9000
- real-time operating system.
- OS-9000/Rave sells for $750. For more information, contact Microware Systems,
- 1900 NW 114th St., Des Moines, IA 50325-7077, (515) 224-1929, FAX (515)
- 224-1352.
-
-
- EMS Revises C Utilities Library
-
-
- EMS Professional Shareware Libraries has been shipping a revised version of
- the Library of PD/Shareware C Utilities, which include more than 400 public
- domain and shareware C utility files.
- Each file in the library is described and indexed in a database that
- accompanies the library. The library contains a variety of types of files,
- including AI, array, benchmark, bit manipulation, C++, communication,
- compression, database, date/time, debugger, disk management, documentor, DOS,
- editor, function library, games, graphics, mathematics, memory management,
- mouse, printer, network, security, tutorial, window, and menu.
- The library sells for $79.50. For more information, contact EMS Professional
- Shareware Libraries, 4505 Buckhurst Ct., Olney, MD 20832, (301) 924-3594; FAX
- (301) 963-2708.
-
-
- DOCZ Assists Software Development
-
-
- DOCZ from Software Toolz allows the sharing and reuse of software modules by
- automating the documentation of subroutine libraries and programs. The
- documentation can be contained in the same file as the source code.
- DOCZ also builds on-line help libraries. Program editors can be interfaced to
- DOCZ to extract function call prototypes from the help libraries while
- editing.
- DOCZ helps developers transport help libraries and documentation from one
- platform to another. Its generated documentation contains all the information
- needed to create an object-oriented development environment. DOCZ supports all
- major languages.
- DOCZ sells for $195. For more information, contact Software Toolz, 8030 Pooles
- Mill Drive, Ball Ground, GA 30107-9610, (404) 889-8264.
-
-
- JMI Expands C Executive Capabilities
-
-
- JMI Software Consultants has introduced new versions of C Executive, a
- real-time, multitasking, ROMable kernel. The new versions of C Executive
- support Advanced Micro Device's Am29050, Intel's i960, and the Inmos (SGS
- Thomson) transputer.
- All versions of C Executive offer the optional DOS compatible file system,
- CE-DOSFILE. With this file system, any CISC or RISC microprocessor can be used
- to maintain MS-DOS file systems on-line. CE-DOS-FILE is compatible with MS-DOS
- file system media.
- For more information, contact JMI Software Consultants, 904 Sheble Lane, P.O.
- Box 481, Spring House, PA 19477, (215) 628-0846.
-
-
- Magee Adds Data Entry Features to Screen Manager
-
-
- Magee Enterprises has released the Screen Manager Professional v2.0, an
- advanced interface design toolbox for C programmers. SMP v2.0 data entry
- features include field-oriented data entry, user attachable validation
- functions, integration with context-sensitive help, and an interactive field
- building utility.
- Other features of SMP v2.0 include unlimited number of windows, event-driven
- mouse support, context-sensitive help, linkable libraries, and a tutorial with
- more than 100 examples of code. SMP v2.0 also offers a comprehensive menu
- system, recording and playback of keystrokes, and shadowing and overlapping of
- windows.
- For more information, contact Magee Enterprises, 2909 Langford Rd. Suite A600,
- Norcross, GA 30071-1506, (404) 446-6611; FAX (404) 368-0719.
-
-
- Air Data Offers Products For 80x86 Embedded Systems Development
-
-
- Air Data is now distributing two new products for embedded software
- development.
- Interactors is a concurrent object-oriented environment for C++. This
- real-time executive is based on multi-party interactions. The real-time
- facilities are added to off-the-shelf C compilers in the form of a source
- library. Interactors is compatible with both Zortech C++ v2.0 compiler and the
- Borland Turbo C++ compiler for MS-DOS.
- Zlocate is a C++ locator for 80x86 embedded software development. The locator
- converts MS-DOS/Zortech's loadable code into absolute code format, suitable
- for programming ROMs.
- These two products were developed by Objective:Systems, P.O. Box 265, Ville
- Mont-Royal, Quebec, Canada H3P 3C5. For more information, contact the
- distributor, Air Data, 4440 Old Orchard St., Montreal, Quebec H4A 3B4, Canada
- (514) 484-0390.
-
-
-
- Software through Pictures Available For Sun Workstations
-
-
- Interactive Development Environments has made available Software through
- Pictures Release 4.2 under the Open-Windows v2.0 application environment from
- Sun Microsystems. The software is also available on the SPARCstation 2
- workstations.
- Software through Pictures under OpenWindows allows users to set up an
- X11-based software development center, using the full range of Sun platforms
- from the SPARCserver 490 to a SPARCstation SLC, combined with any X11 display
- server from other manufacturers.
- Single license prices for Software through Pictures range form $5,000 to
- $21,000, depending on the configuration. For more information, contact
- Interactive Development Environments, 595 Market St., 10th Floor, San
- Francisco, CA 94105, (415) 543-0900; FAX (415) 543-3716.
-
-
- XDB Offers SQL Precompiler For Windows 3.0
-
-
- XDB has introduced its SAA-compatible SQL precompiler for C. XDB will bundle
- the C precompiler with XDB-C and XDB-Windows software developer toolkits. With
- these toolkits, C programmers can build sophisticated applications for XDB's
- SQL database servers.
- The C precompiler can be configured to be DB2, SAA, ANSI, Oracle, or
- XDB-compatible and is capable of running under DOS, OS/2, and Windows 3.0. The
- C precompiler provides support for both embedded and dynamic SQL. XDB also
- allows applications to mix XDB's library of functions found in XDB-C plus
- XDB's SQL engine for Windows 3.0.
- XDB-Windows is priced at $750. XDB-C is priced at $595. For more information,
- contact XDB Systems at (301) 779-6030.
-
-
- Database Software Consultants Updates Network Library
-
-
- Database Software Consultants has released dCL-Net Library v4.0 and changed
- the name to dMill network kit for Clipper. In addition to this library, the
- dMill network kit for dBase III Plus and the dMill network kit for FoxPro/LAN
- will also be available.
- These network kits offer an upgrade path, including an education component, a
- techniques component, and a tools component. Product manuals for the libraries
- are comprehensive and heavily indexed.
- For more information, contact Database Software Consultants, P.O. Box 8380,
- Austin, TX 78713-8380, (512) 477-3423.
-
-
- ZIM v3.11 Complies With 88open Standards
-
-
- ZIM, Sterling Software's Entity-Relationship-based 4GL/RDBMS has been
- officially certified as compliant with 88open standards.
- ZIM is an application development environment delivering complex, multi-user
- applications. ZIM is available on a range of hardware platforms and operating
- systems. The ZIM family of products includes Runtime, Query-Runtime, and
- SQL-compatible versions.
- For more information, contact Sterling Software, 36 Antares Dr., Suite 500,
- Ottawa, Ontario K2E 7W5; (613) 727-1397; FAX (613) 727-6940.
-
-
- Microsoft Offers Upgrade to Lattice C Users
-
-
- Microsoft is offering Lattice C compiler users a $125 upgrade to the Microsoft
- C Professional Development System v6.0. No new features will be added to
- Lattice C v6.05.
- Version 6.0 upgrades are available directly from Microsoft. Proof of Lattice C
- ownership is required. For more information, contact Microsoft, One Microsoft
- Way, Redmond, WA 98052-6399, (206) 882-8080; FAX (206) 883-8101, or contact
- Microsoft customer service at (800) 541-1261.
-
-
- Motorola Supports High Security of UNIX Systems
-
-
- The Motorola Computer Group will support the high security and multiprocessing
- features of the UNIX System V r4 operating system to be released by AT&T.
- Motorola will incorporate these capabilities into its System V/68 and System
- V/88 UNIX operating systems for computers based on Motorola's own MC68000 and
- M88000 microprocessor families.
- For more information, contact Motorola at (602) 438-3576.
-
-
- ParcPlace Systems Ships Objectworks
-
-
- ParcPlace Systems is now shipping the newest releases of its object-oriented
- programming systems, Objectworks\C++ and Objectworks\Smalltalk.
- The company also established ParcPlace Partners, a value-added business
- program for third-party Smalltalk application developers and systems
- integrators. The ParcPlace Partners program offers a variety of marketing and
- technical services that support the development, porting, and selling of
- Smalltalk applications.
- For more information, contact ParcPlace Systems, 1550 Plymouth St., Mountain
- View, CA 94043, (415) 691--6700; FAX (415) 691-6715.
-
-
- Informix Supports DEC Multiprocessor System
-
-
- Informix Software is planning to make its leading line of information
- management software products available on Digital Equipment Corporation's
- recently introduced application DEC 433MP multiprocessor platform. The new
- platform will run the SCO UNIX System V version of the UNIX operating system.
- For more information, contact Informix Software, 4100 Bohannon Dr., Menlo
- Park, CA 94025, (415) 926-6300.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- We Have Mail
- Dear Mr. Plauger,
- Since purchasing Turbo C++ in the middle of this year, I have had a love-hate
- relationship with it. The qsort function is one of those. I first found this
- when playing around with Leor Zolman's Mini Data Base article.
- My solution in this case was different than that of Mr. Irani. (See mdbedit. c
- in Listing 1.) I passed the parameters exactly as Turbo C++ required, then
- "changed" them with casts inside the function. It would appear to me that the
- problem here has to do with the way Borland handles void. Turbo C seems to
- avoid this with a very loose prototype in <stdlib.h>.
- Other problems with Turbo C++ have been its handling of arrays and a somewhat
- buggy IDE.
- The IDE on occasion fails to restore the mouse cursor properly, leaving a
- black square on the screen where it was last clicked. (If you use an old, or
- nonMicrosoft driver, the mouse cursor may disappear completely, while the
- mouse remains active.) When debugging, the IDE sometimes becomes lost,
- requiring exiting, and restarting. Part of it seem to think the program is
- still executing, while part of it rightly knows it has successfully finished.
- I do not like the editor defaults on the IDE. Thankfully most of them are
- changeable. However, some must be changed from TCINST while others must be
- changed from within the IDE.
- Overall I like the Turbo C++ product, but don't think it is as perfect as much
- of the press out there has implied.
- I am not a professional programmer so I do not claim to be any expert. However
- I hope some of this may give some insight to others.
- I am a self-taught electronics technician of 20 plus years, now working on
- becoming a self-taught programmer. I program in C, Assembly, QuickBASIC, and
- Pascal (in that order).
- Sincerely,
- W. Paul Mills
- 4638 N.W. 35th St.
- Topeka, KS 66618-3609
- Thanks for the information. I haven't researched this, so I'm mostly just
- rattling, but I wonder if the qsort problem isn't just one manifestation of
- the significant difference between C++'s and C's attitudes toward types and
- type checking. C++ strives to be a strictly typed language -- C doesn't even
- pretend. The end result must be that many common C practices won't be
- acceptable C++ code.
- -- rlw
- Dear Mr. Ward:
- I recall seeing in a back issue of The C Users Journal that the yacc/bison and
- lex/flex disks were the most often requested from your disk library. Since
- this seems to be what many of your readers want, I was wondering if you are
- planning any more articles on lex and yacc? It seems to me that you could have
- articles showing ANSI-C and C++ grammars, for instance. Or how about something
- on HOC7, which was mentioned on page 77 of the July 1989 Journal.
- By the way, where can I find HOC7? I couldn t find it on Compuserve, nor on
- any of the local BBS s around here. Do you have it in your disk library or on
- any of your mayazine code disks that I could order? Thanks in advance for any
- assistance you can offer.
- Sincerely,
- William Pierpoint
- Pierpoint Software
- P.O. Box 2198
- Camarillo, CA 93011
- Of course I'm willing to publish some more articles about lex and yacc. I
- suspect a full blown ANSI or C++ grammar would be a little beyond the scope of
- a single magazine article, but we might find a way to publish it anyway.
- I suspect you'll need to contact Victor Volkman directly to get a copy of
- HOC7. In case you aren't familiar with it, I strongly recommend Kernighan and
- Pike's The Unix Programming Environment. The book develops six increasingly
- sophisticated implementations of HOC (a High Order Calculator) as lex and yacc
- case studies.
- -- rlw
- Dear CUJ:
- The December issue of CUJ arrived today. I opened it to the table of contents,
- and turned eagerly to the article by Art Shipman, "Debugging with two
- monitors." After scanning the article two or three times, I asked, "Where is
- the hardware info on how to connect the second monitor?" Listening closely, I
- heard no answer.
- Reading the author's bio, I noticed he is a consultant, and his address was
- given. Does this mean I am to contact him and enlist his professional services
- to find out how to connect the second monitor?
- I also scanned the sidebar "Utilities for a second monitor" on page 31, and
- again found no mention of hardware add-on requirements; a new board? a
- connector into a printer port? does my PC already have a second monitor port
- which I just plug into? if so, why? ...
- Can you help me out? (Which way did I come in, you ask?)
- Yours in frustration,
- Homer B. Tilton
- 8401 Desert Steppes Drive
- Tucson, Arizona 85710
- As Art Shipman says in the opening paragraph of that story, you must pick up a
- second video card to run a second monitor. You must, however, be careful to
- select a combination of cards that can be configured to reside at separate
- memory and port addresses and that can be placed on independent interrupts.
- Since the second monitor is always a "non-standard" system addition, this
- configuration must be customized to your particluar system. The documentation
- with more sophisticated and special purpose video systems will often explain
- how to configure these cards.
- I hope this helps.
- -- rlw
- Don Libes,
- Thanks for the article about software timers in the November 1990 CUJ. I
- enjoyed it. A couple of observations:
- In Listing 3 and Listing 5, you disable interrupts while accessing the
- timers[] array. But each function has an early return that, if taken, will
- leave interrupts disabled. For example, in listing 3, the line
- if (!t-inuse) return;
- will cause an early return without reenabling interrupts.
- Also, the enable/disable of interrupts is a necessary precaution when
- accessing the timer data structures. But it is sufficient only if you are
- running on a single processor system that is not multiprocessing, or that
- cannot preempt execution when you are executing inside a timer function, or in
- any system where disable_interrupts( ) also prevents process preemption. It is
- not sufficient for:
- any system where process preemption (timesharing or multitasking) continues
- even though disable_interrupts( ) is called, and it is
- a multiprocessor system, if the timer structures are in shared memory
- a single processor multi-process or multi-threaded, if the execution can be
- preempted while in a timer function.
- In such environments, you need an additional semaphore to restrict execution
- of these critical areas to no more than one process at a time. For example, if
- locks(s)/unlocks(s) are functions that lock/unlock a critical section to one
- process at a time, based on the condition of the semaphore s,and if lock( )
- waits indefinitely until the semaphore becomes available (a simplification),
- then the code could look like this:
- void
- timer_undeclare(t)
- struct timer *t;
- {
- disable_interrupts();
- lock(s);
- ....
- unlock(s);
-
- enable_interrupts();
- }
- Looking forward to more articles from you!
- Randy D. Miller
- Don replies:
- Oops, you are correct about the early return without re-enabling interrupts.
- As far as running in multiprocessing environments, I thought this was obvious.
- However, I don't mind if it is stated more obviously as you do in your letter.
- (-DL)
- Dear Editor,
- The "Quick Take" of Menuet in the December 1990 issue was a disaster: the
- review is more like a Quick Mistake! Even though my company sells a product
- that competes with Menuet, I have to come to the defense of the Menuet
- developers. The review is inaccurate in some of its comments about Menuet, and
- way off base in its reading of the GUI market place. Contrary to what the
- review states, there is plenty of need for GUIs other than MS-Windows, HP's
- New Wave and DRI's GEM.
- I do agree with one thing about the article: Menuet and MetaWindow combined
- will add 200K to an application. However, that's still a lot less than the 4
- megabytes of memory required for decent performance with any MS-Windows
- application!
- The review-said: "Font support is noticeably lacking...". Wrong! Any package
- based on the MetaWindow toolkit, lets you have many different fonts, sizes,
- etc.
- The review implies a three button mouse is "the cat's meow" (sorry). Our user
- research indicates a one button mouse is easier to use and that's what end
- users want! In fact, there are many end users out there who are "mouse
- phobic." Having to use three buttons would give them a nervous breakdown.
- Isn't the whole idea of a GUI to make the application easier to use for the
- end user?
- The review said: "Since Menuet requires MetaWindow as a prerequisite, the
- market will be limited to those already interested in MetaWindows. Wrong! Many
- of our customers (about 50%) are new to graphics, and are looking for
- intelligent alternatives to MS-Windows. There are still many C programmers out
- there who like to write small, compact programs, and get them done before the
- turn of the century.
- Most developers I talk to say MS-Windows is a pain in the tail to program in!
- They looked at Windows and don't want to pay the learning curve (of perhaps
- months!), or deal with such a memory hog. They need to develop applications
- that do a specific task, don't take 4MB of memory, and are easy to use. In
- many cases, there are better GUI development tools than Windows 3.0!
- The reviewer's comment about Menuet not having intertask communication is
- myopic at best. Obviously, he is enamored with leading edge technologies
- (sometimes called bleeding edge!). However, most of the "trailing edge"
- (probably 90% of the DOS market) doesn't need intertask communications. This
- is not a "Bells and Whistles" war: developers are looking for tools that make
- them productive. Is that really MS-Windows in every case? I think the reviewer
- lost site of what developers and end users really need.
- This is another example of reviewers not really understanding the market. They
- evaluate products based on the number of bells and whistles, not on whether it
- meets a need! If cars were reviewed that way, then the reviewer would
- recommend that everyone buy a Rolls Royce! Good development tools are not
- based on the number of bells and whistles, but on whether they make the
- developer more productive!
- The statement, "there seems to be little need for another me-too GUI for DOS,"
- I find particularly naive, close-minded, and just plain stupid! The reviewer
- has set up a false standard and implied all GUI development products should
- meet this standard. That's crazy! The reviewer also implied that MS-Windows is
- the ultimate development tool and that there is no reason to look at anything
- else. That's frightening! This review has done an incredible disservice to
- your readers. There is a great demand for alternative GUI development
- products, but you would never know it by reading reviews like this one.
- Sincerely,
- Tod Brannan, President
- Oxford Consulting Group
- 1010 Oxford Street
- Berkeley, CA 94707
- You are right -- and you will see no more "Quick Takes" in this magazine. I
- made the mistake of allowing what had been commissioned as a very cursory
- "internal guidance only" survey to be repackaged for the magazine. While
- cursory examinations (complete with the kinds of mistakes that accompany a
- hurried scan of a complex project) are useful to some of our non-technical
- staff, they are totally inappropriate for this magazine and violate all my own
- rules about reviews.
- I appologize to my readers and to all the vendors who were mentioned in the
- December Quick Take.
- And Tod, you must be one hell of a nice guy to get so upset about how a
- competitor was treated! I'm glad I have people like you reading my magazine.
- -- rlw
-
- Listing 1
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <ctype.h>
- #include "mdb.h"
-
- static void fix_db(void);
-
- #define LIST_RECS 1 /* Edit menu action codes */
- #define NEXT 2
- #define PREVIOUS 3
- #define MODIFY 4
- #define NEW 5
- #define DELETE 6
- #define UNDELETE 7
- #define QUIT 8
- #define SELECT 9
- #define FIX 10
-
- static struct menu_item edit_menu[] = {
- {LIST_RECS, "List Records"},
- {NEXT, "Go to Next Record"},
- {PREVIOUS, "Go to Previous Record"},
- {MODIFY, "Modify Current Record"},
- {NEW, "Add New Record"},
- {DELETE, "Delete Current Record"},
- {UNDELETE, "Un-Delete Current Record"},
- {SELECT, "Select Record (by Record Number)"},
- {FIX, "Fix Up Database"},
- {QUIT, "Return to Main Menu"},
- {'\0'}
- };
-
-
- /**************************************************
- * Function: edit_db
- * Purpose: Perform operations on current
- Database
- * Parameter: Database Name
- * Return Value: None
- */
-
- void edit_db(char *db_name) {
- int cur_rec = 0; /* current record number */
- struct record *rp;
- char buf[150];
- int i;
-
- while (1) {
- rp = RECS[cur_rec];
- printf("\nDatabase: %s\n", db_name);
- if (n_recs)
- {
- printf("Current Record is #%d", cur_rec);
- if (!rp->active)
- printf(" (Deleted)");
- printf(":\n");
-
- printf("Name: %s %s\n", rp->first, rp->last);
- printf("ID#: %ld\n", rp->id);
- printf("Age: %d\n", rp->age);
- printf("Gender: %c\n", rp->gender);
- printf("Salary: $%.2f\n", rp->salary);
- }
-
- switch(do_menu(edit_menu, "Edit Menu")) {
-
- case LIST_RECS:
- for(i = 0; i < n_recs; i++)
- printf("%4d: %s%s\n", i, RECS[i]->last,
- RECS[i]->active ? "" : "(Deleted)");
- printf("Press RETURN to continue:");
- gets(buf);
- break;
- case NEXT: /* find next active record: */
- for (i = cur_rec + 1; i < n_recs; i++)
- if (RECS[i]->active) /* skip inactives */
- break;
- if (i == n_recs) { /* over the top? */
- printf("\aAt end of file.\n");
- break;
- }
- cur_rec = i;
- break;
- case PREVIOUS: /* find previous active record: */
- for (i = cur_rec - 1; i >= 0; i--)
- if (RECS[i]->active) /* skip inactives */
- break;
- if (i < 0) { /* "under the bottom"? */
- printf("\aAt beginning of file.\n");
- break;
- }
-
- cur_rec = i;
- break;
- case NEW:
- if (n_recs+1 > max_recs) {
- printf("Maximum # of records ");
- printf("(%d) reached.\n", max_recs);
- break;
- }
- if ((rp = alloc_rec()) == NULL) {
- printf("Out of memory. Try Fixing ");
- printf("Database first...\n");
- break;
- }
- /* make new record current: */
- cur_rec = n_recs++;
- RECS[cur_rec] = rp;
- rp->active = TRUE;
- strcpy(rp->last,""); /* initialize the record */
- strcpy(rp->first,"");
- rp->id = 0;
- rp->age = 0;
- rp->gender = ' ';
- rp->salary = 0.0F; /* fall through to MODIFY */
- case MODIFY:
- printf("Last Name [%s]: ", rp->last);
- if (strlen(gets(buf)) > 0)
- strcpy(rp->last, buf);
- printf("First Name [%s]: ", rp->first);
- if (strlen(gets(buf)) > 0)
- strcpy(rp->first, buf);
- printf("ID# [%ld]: ", rp->id);
- if (strlen(gets(buf)) > 0)
- rp->id = atol(buf);
- printf("Age [%d] ", rp->age);
- if (strlen(gets(buf)) > 0)
- rp->age = atoi(buf);
- printf("Gender [%c]: ", rp->gender);
- if (strlen(gets(buf)) > 0)
- rp->gender = (char)toupper(*buf);
- printf("Salary [%.2f]: ", rp->salary);
- if (strlen(gets(buf)) > 0)
- rp->salary = (float) atof(buf);
- break;
- case DELETE:
- if (!rp->active) {
- printf("Record is already deleted.\n");
- break;
- }
- printf("Press 'y' to delete record,\n");
- printf("anything else to abort: ");
- gets(buf);
- if (tolower(*buf) == 'y')
- rp->active = FALSE;
- break;
- case UNDELETE:
- if (rp->active) {
- printf("Record is not deleted.\n");
- break;
- }
-
- rp->active = TRUE;
- printf("Record restored.\n");
- break;
- case SELECT:
- printf("Enter new record number: ");
- i = atoi(gets(buf));
- if (i < 0 i > n_recs) {
- printf("Record # out of range.\n");
- break;
- }
- cur_rec = i;
- break;
- case FIX:
- fix_db(); /* clean up database */
- break;
- case QUIT:
- return;
- }
- }
- }
-
- /**************************************************
- *
- * Function: fix_db
- * Purpose: Purge deleted records, sort db
- * Parameters: None
- * Return Value: None
- */
-
- static int compar(const void *a, const void *b);
-
- static void fix_db(void) { /* File Fix module */
- int i, new_n_recs;
-
- for (i = 0, new_n_recs = 0; i < n_recs; i++) {
- RECS[new_n_recs] = RECS[i];
- if (RECS[i]->active)
- new_n_recs++;
- else
- free(RECS[i]);
- }
- n_recs = new_n_recs;
- qsort(RECS, new_n_recs, sizeof(struct record *), compar);
- }
-
- /**************************************************
- * Function: compar
- * Purpose: Comparison function for qsort(),
- * sorting simply on last name
- * Parameters: Two pointers to record pointers
- * Return Value: As per strcmp()
- */
-
- static int compar(const void *a, const void *b) {
- struct record **p1 = (struct record **)a;
- struct record **p2 = (struct record **)b;
- return strcmp((*p1)->last, (*p2)->last);
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Embedded Real-Time Multitasking Kernel
-
-
- Joel Halbert
-
-
- Joel Halbert has a B.S. in electrical engineering from the University of Texas
- in Austin, Texas. He has 12 years experience in the design and implementation
- of computer hardware and software. He may be contacted at 1407 Meadowmear,
- Austin, TX 78753.
-
-
- In implementing software for embedded systems, several items can ease the job.
- These include a kernel to implement a virtual machine, a high-level language
- (preferably C), and a good in-circuit emulator. I describe here a simple
- kernel for the Motorola 6801 microprocessor that supports tasks written in C.
- I also describe how to interface between the C routines and assembly language.
- I use the assembler, compiler, and in-circuit emulator (ICE) supplied by
- American Automation.
- The kernel is very simple. It implements only three calls. All kernel code is
- written in assembly. Calls to the kernel conform to the C subroutine call
- interface, however, so that tasks written in C can call the kernel directly.
- C is a particularly good language to write embedded system code. It has the
- bit manipulation needed to talk directly to hardware registers. Its high-level
- features enable programmers to structure code for easier debugging,
- maintenance, and readability. In addition, C generates less code than other
- high-level languages.
-
-
- 6801 Architecture
-
-
- The 6801 microcontroller, designed in the late 1970s, is a relatively old
- architecture. It is cheap, easy to use, and manufactured by several
- semiconductor houses. The chip features:
- a serial communications interface
- a 16-bit three-function programmable timer
- choice of single-chip or expanded 64K-byte address space
- 2,048 bytes of ROM
- 128 bytes of RAM
- 29 parallel I/O lines of which two can be defined as handshake lines
- internal clock generator with divide-by-four output
- TTL compatible inputs and outputs
- Figure 1 shows the registers for the 6801. Two eight-bit accumulators (called
- A and B) can be concatenated into one 16-bit register referred to as D. The
- index register (called X), the stack (called S), and the program counter
- (called PC) all are 16 bits wide. The condition code register (called CC) is
- eight bits wide. Figure 2 shows the condition code register in greater detail.
- The 6801 operates in eight different modes. The operating mode is determined
- just after chip reset by levels on three of the I/O pins. Three major
- divisions of these modes are:
- Single Chip -- All four ports are configured as parallel input/output ports.
- The MPU functions as a self-contained microcomputer with no external address
- or data bus.
- Expanded Non-Multiplexed -- The MPU can directly address modest amounts of
- external memory (up to 256 bytes).
- Expanded Multiplexed -- The MPU can address the entire 64K byte address space.
- In addition, 13 I/O lines are available.
- The kernel I describe here uses Expanded Multiplexed mode. That supports
- external RAM and ROM that can reside in sockets for expandability and ease of
- maintenance.
-
-
- Kernel Architecture
-
-
- I consider all code except for the tasks part of the system. The rest is task
- code. To initiate multitasking, the application program calls a setup routine,
- giving the starting function pointer for each task. This routine allocates
- separate stack areas (distinct from from system stack) for each of a fixed
- number of tasks. It then sets up a current stack pointer and execution pointer
- for each task. After the initialization of four tasks, for example, the system
- can concurrently execute the tasks with code similar to
- while (true)
- {
- continue_task(0);
- continue_task(1);
- continue_task(2);
- continue_task(3);
- }
- The continue_task function (Listing 1) transfers control to the numbered task
- and returns when the task calls suspend. The continue_task and suspend
- functions also save and restore any register variables on the task stack. You
- can expand the while (true) test to check for various stopping conditions,
- including global status flags set by the tasks. This kind of multitasking is
- non-preemptive. It requires that the tasks call suspend frequently. Since each
- task has its own stack, the call to suspend may occur at any level of
- subroutine nesting.
- The kernel may appear to contain a lot of functionality to pack into two pages
- of assembly code, but it is actually much simpler than a general real-time
- multitasking kernel. For example, this task scheduler provides no means for an
- external event to activate a task. Every task is expected to have control for
- a short time in every loop; the task itself must check for activation
- conditions. Also, the scheduler does not provide signaling between tasks. The
- tasks do, however, use message queues and global tables to implement this type
- of signaling.
- Since all tasks are linked with the kernel as reentrant functions in one
- program, special care must be taken when referencing static variables (both
- global and non-global). In general, a tasks's use of static variables should
- be limited to deliberate inter-task communication. Variables allocated off the
- stack are automatically private to the task.
- It may be helpful to see a diagram of the stack structure at various times of
- the execution of the kernel. At startup, memory is uninitialized. No stacks
- for task are yet defined. When the startup code calls the initialization
- routines, the task's stacks are defined, as shown in Figure 3.
- Two arrays, task_tab and fram_tab, hold the stack pointers and frame pointers
- for each task. The kernel saves the system stack pointer in sys_stk and the
- system frame pointer in sys_sf. When a task is in control, it is using its own
- stack and frame pointers. When the task calls suspend, the kernel saves these
- pointers in the task table and retrieves those for the system. The task
- transfers control to the system by setting task mode to 0 and executing the
- next task in the master while loop in main.
-
-
- Data Flow
-
-
- The tasks communicate with each other via circular queues and system tables.
- At system startup, all queues are empty and the system tables contain default
- values. Serious operation starts when the computer receives operating
- parameters via the serial lines. This causes an interrupt routine to insert
- characters into a circular buffer called in_queue.
- When activated, the message_in (Listing 2) task sees that characters are in
- in_queue. The task determines what kind of message has been sent. After
- decoding the message, the task loads a return message into another circular
- buffer, called out_que.
-
- When the task message_out (Listing 3) is activated, it sees that characters
- are in out_queue. The task removes the message from out_que and reformats it
- as a message suitable for serial transmission -- with CRC bytes generated and
- transparency characters inserted. The formatted message is then put into the
- xmit_que for sending over the serial line. The task msg_out then calls
- send_msg, which sends out the first character, enables the send character
- interrupt, and waits until all characters of the message are sent before
- disabling the send character interrupt.
- The task key_brd in Listing 4 checks for key strokes on the key pad. It works
- with the counter interrupt routine. If a key is being pressed, the interrupt
- routine rcv_key decodes the key and puts it into the global variable
- keyboard_port. When activated, key_brd checks for a character in
- keyboard_port. If key_brd finds a character, it inserts the character into
- key_que.
- The task display in Listing 5 reads the queue key_que to determine whether or
- not the the key should affect the display. In addition, display loads a buffer
- containing key strokes for serial transmission.
-
-
- Task Structure
-
-
- All tasks must be organized the same way for this scheme to work. The basic
- structure is an endless loop:
- while (true)
- {
- /* all statements
- * that make up the
- * task
- */
- }
- All tasks communicate via shared memory (global memory) using circular queues
- and semaphores. Also, the tasks read system tables for setting operational
- parameters.
-
-
- Interfacing Assembly With C
-
-
- In this project, I interfaced assembly with C using two different approaches
- -- writing functions completely in one language and embedding in-line assembly
- statements within C code.
- When writing a function in assembly, the most important aspect is the function
- calling convention for the particular C compiler you are using. With the
- American Automation C compiler that I used for this project, the parameters in
- a call are handled differently depending on the number of parameters passed.
- If you are passing only one parameter to the subroutine, then the parameter is
- passed in the D register. If you are passing more than one parameter, then the
- leftmost parameter is passed in the D register, and the rest of the parameters
- are passed through the stack. Examine the following call:
- init_task(1,&message_in);
- The leftmost paramater, 1, is passed in the D register, and &message_in, the
- address of the subroutine message_in, is passed on the stack.
- The following example shows the second approach, embedding assembly within a C
- function. The example illustrates how to reference the same variable by C code
- and assembly. A typical variable declaration in C is
- extern unsigned char VARIABLE;
- Compilers usually transform the variable name when translating the C
- statements to assembly. A common transformation is to add an underscore or
- some other character either before or after the name. The American Automation
- compiler appends a question mark ? to a variable name. When referencing the
- variable within assembly, I use the following statement:
- xref VARIABLE?
- Each compiler has its own way to embed assembly language in C. Turbo C uses a
- keyword, asm, to indicate to the complier that the following statement is
- in-line assembly. The American Automation compiler uses the keyword #asm to
- indicate the beginning of a series of assembly language statements and the
- keyword #endasm to indicate the end of a series of assembly language
- statements. American Automation recommends that all #asm statements be
- immediately preceeded with a semicolon. See Listing 5.
- In assembly language, an address gets assigned to each variable that you
- define. A final hardware address is determined for a variable name using a
- combination of SECTION and DS (define storage) declarations. The American
- Automation C compiler supplies an assembly language startup file that is
- always linked first in front of all other files. This file is where you would
- assign memory addresses to hardware registers residing on your board. The
- following demonstrates a SECTION statement in an assembly language source
- file.
- SECTION IO,?,DATA ; data (I/O) start address
- You are defining a section of memory called IO.? means the address will be
- defined at link time, and DATA means it is a data section. IO has previously
- been defined as 3000. You are telling the assembler to create a section of
- memory starting at address 3000 that will be uninitialized data.
- Now you can begin to use DS statements to assign variables to memory
- locations. For example:
- XDEF I_O?, VARIABLE?,OTHER?
- I_O?: DS 1
- VARIABLE?:
- OTHER?: DS 1
- I have defined three variables -- I_O, VARIABLE, and OTHER. Notice that the
- XDEF directive enables other files that are linked with this file to know
- about these variables. Note also that the DS statements come after the SECTION
- statement. Any other DS statements that follow will define storage in the IO
- section until another SECTION statement is encountered.
-
-
- Interrupts
-
-
- Usually, embedded systems software deals with interrupts. These interrupts
- come from a variety of sources. The code I show here deals with interrupts
- from the programmable timer and the serial communications interface.
- The programmable timer is set up to be a free running counter that will
- generate an interrupt whenever the contents of the counter equal 0. This timer
- generates delays and also reads the keypad for any keys that are pressed. The
- serial communications interface generates an interrupt whenever a character is
- received by the serial port receive buffer and whenever the transmit buffer is
- empty.
- You incorporate interrupts by the following method. The startup file assigns
- the addess of the interrupt routine to vector locations by the method shown in
- Listing 6. The first statement is a SECTION directive that creates a section
- called VECTORS at location . It is followed by define words containing
- addresses of the routines that will be invoked when the vector is executed. In
- addition, the startup file contains an assembly language routine that provides
- the glue needed to call a C subroutine, which actually does the work in the
- interrupt handler. There is an assembly language routine for each vector that
- may be needed. The routine for the timer is shown in Listing 7.
- With this setup, you can write your interrupt routine much like any other C
- function.
- Figure 1
- Figure 2
- Figure 3
-
- Listing 1 (multask.s)
- title multask.s
- name mtask
- *********************************************************************
- *
- * NAME :
-
- *
- * DESCRIPTION : multasking routines for the motorola 68xx series
- * of microcomputers
- *
- * init_task(task#, task_address) /* called from system mode */
- * continue_task(task#) /* called from system mode */
- * suspend() /* called from task mode */
- *
- * The task_mode flag to keep track of which mode is in effect
- * allows the use of subroutines hat are called by both system
- * and task code. If those subroutines call suspend(). then in
- * the case of a system mode caller syspend() must return
- * control immediately.
- *
- *********************************************************************/
-
- * COMMON DEFINITIONS */
- stksize equ 255
- tasks equ 4
-
- xref SF ;Stack Frame
-
- SECTION DATA,?,DATA
-
- sys_stk dw 0
- sys_sf dw 0
- task_num dw 0
- task_mode db 0
- tstk dw 0 ; temporary stack storage
- task_tab ds tasks*2 ; tasks words
- fram_tab ds tasks*2 ; frame words
- SECTION STACK,?,DATA
- xdef _task_stks, task_stks
- _task_stks
- task_stks ds stksize*(tasks+1)
- pad2 dw 0 ; end boundry for data
- xdef _sp?
- _sp?: DS 2
- pad3 dw 0 ; end boundry for stacks
-
- SECTION PROGRAM_CODE,?,CODE
-
- xdef init_task?
- xdef continue_task?
- xdef suspend?
-
- *********************************************************************
- *
- * NAME : init_task(task_number, task_address)
- *
- * DESCRIPTION: initializes the task specified by the task address
- * saves the stack pointer into sys_stack
- * saves the task number into task_num
- * calculates the beginning of the task's stack
- * loads the stack pointer with the results of the preceeding
- * calculation
- * sets task_mode to one to indicate that we are in task mode
- * push address of callsus to new stack in case the task returns
- * this prepares the return address of a hung task that
-
- * repeatedly gives up control by calling suspend
- * this routine drops through to suspend.
- * so in effect this routine sets up a task with a stack then
- * prepares the stack for the next call as if it were running
- * then suspends itself.
- *
- init_task?:
- sts sys_stk ; Save the system stack
- std task_num ; save parameter task number for later
- ldd SF ; get stack frame pointer and save into
- std sys_sf ; system variable
- ldd task_num
- ldaa #stksize ; make calculation of task_num times
- mul ; stack size then add to beginning
- addd #(task_stks+stksize) ; of stack pool
- std tstk ; put d into temporary stack storage
- lds tstk ; get new stack from temp. stack storage
- subd #3 ; adjust frame pointer for next routine
- std SF ; load new stack frame pointer
- inc task_mode ; put us into task mode
- ldd #callsus ; get address of suspend and
- pshb ; load it into the new stack
- psha ;
- pshx ; save stack into new stack
-
- * fall through to next routine (suspend)
-
- *********************************************************************
- *
- * NAME : suspend()
- *
- * DESCRIPTION: suspend the current task by saving the current stack
- * stack pointer into the task table and then getting the
- * system stack and transfer control to the system, setting
- * task mode to 0 and start executing the next task in the
- * master while loop in main.
- *
- suspend?: ; see if we are in task mode if s?o
- tst task_mode ; go to suspend task
- bne sustsk ; else return
- rts
- sustsk:
- ldd task_num ; get the task number and multiply by two
- lsld ; for indexing purposes into the task table
- ldx #task_tab
- abx
- sts x ; store the stack into the proper table entry
- ldx #fram_tab ; get address of frame table and store SF
- abx ; into indexed address of frame table array
- ldd SF
- std x
- lds sys_stk ; restore the system stack
- ldd sys_sf ; restore stack frame pointer
- std SF
- clr task_mode ; go into system mode
- rts ; and return
-
- *********************************************************************
- *
-
- * NAME : continue(task_number)
- *
- * DESCRIPTION: continues the task specified by the task number
- * get the task number in the task table
- * loads the stack pointer at the table index
- * set task mode to one indication we are task mode
- * and returns
- *
- *
-
- continue_task?:
- sts sys_stk ; store the stack
- std task_num ; save into variable task_num
- ldd SF
- std sys_sf
- ldd task_num
- lsld ; multiply task number by 2
- ldx #task_tab ; get address of tasktable into index
- abx ; add (task hum * 2) + table address
- lds x ; load new stack into sp
- ldx #fram_tab ; get the task's frame pointer and update
- abx ; the frame pointer
- ldd x
- std SF
- inc task_mode ; set task mode to task
- rts ; return ( to new task)
-
- *********************************************************************
- *
- * NAME : callsus
- *
- * DESCRIPTION: this routine is in case the task returns all the way
- * back to the main routine.
- *
-
- callsus:
- bsr suspend?
- bra callsus
-
- end
-
-
- Listing 2 (msg_out.c)
- /******************************************************
- * NAME: message_in
- * DESCRIPTION: Text ........
- ******************************************************/
-
- #include "que.h"
-
- extern struct g_queue in_que;
-
- void message_in()
- {
- unsigned char new_msg,temp;
- int i, state;
-
- while (true)
- {
-
- new_msg = true;
- i = 1;
- state = 0;
- /* now look at each character if the character is
- a DLE then the next character is ingored */
- while (new_msg)
- {
- temp = remove_one(&in_que);
- /*
- * implement a state machine to
- * format the incoming message
- * to a form suitable for your application
- */
- }
- /*
- * calculate the crc
- */
- /* determine the address */
- /*
- * determine the type of message
- * and send a reply
- */
- }
- }
-
-
- Listing 3 (key_brd.c)
- /******************************************************
- * NAME : key_msg
- * DESCRIPTION: get a key, decode, put in key queue
- ******************************************************/
- #include "que.h"
-
- extern struct g_queue key_que;
-
- void key_msg()
- {
-
- unsigned char position, key;
-
- position = (unsigned char)0x00;
- while ( 1 )
- {
- while (getkey(&position) < 0)
- suspend();
- insert_one(position,&key_que);
- }
- }
-
- /******************************************************
- * NAME: getkey
- * DESCRIPTION:get key position from interrupt routine
- ******************************************************/
-
- getkey(position)
- unsigned char *position;
- {
- int x;
- /* has there been a read of keypad
-
- * is there a disable msg
- * if there has been a read - then
- * decode and verify the key then return
- * it in x
- */
-
- return x;
- }
-
-
- Listing 4 (display.c)
- /***************************************************
- * NAME display
- * DESCRIPTION: skeleton routine
- * to read key stroke
- ***************************************************/
-
- void t_display()
- {
- unsigned char key;
- int state = 0;
-
- while(1)
- {
- switch (state)
- {
- case 0:
- /*
- * here you would implement a
- * state machine to handle cases of
- * key strokes and their effect on
- * the display
- */
- default:
- break;
- } /* end of switch logic for states */
-
- while (key_que.empty && !reset && !disable_on)
- suspend();
- key = remove_one(&key_que);
-
- }
- }
-
-
- Listing 5
- exam(stuff)
- char *stuff;
- {
- char i;
-
- for (i=6; i>=0; i--)
- {
- I_O = stuff[i];
- #asm /* #asm to indicate start of assembly */
- xref VARIABLE? /* note the external reference */
- STAA VARIABLE? /* to VARIABLE */
- #endasm /* #endasm to indicate end of assembly */
- }
-
- ; /* required semi-colon to preceed #asm */
- #asm
- xref OTHER?
- LDAA OTHER?
- #endasm
- }
-
-
- Listing 6
- SECTION VECTORS,$FFFO,CODE
- _VECTORS:
- DW timer ;$FFFO - SCI
- DW serial ;$FFF2 - TOF (Timer Overflow)
- DW 0 ;$FFF4 - OCF (Output Compare)
- DW 0 ;$FFF6 - ICF (Input Capture)
- DW 0 ;$FFF8 - IRQ1 (or IS3)
- DW 0 ;$FFFA - SWI (Software Interrupt)
- DW 0 ;$FFFC - NMI (Non-maskable Interrupt)
- DW main ;$FFFE - RESET (Hardware Reset)
-
-
- Listing 7
- timer:
- XREF counter_irq?
- LDX PR
- PSHX
- LDX SR
- PSHX
- JSR counter_irq? ; place the name of C interrupt handler here
- PULX
- STX SR
- PULX
- STX PR
- RTI
-
-
- Listing 8 (interupt.c)
- /************************************************************
- * NAME : Interupt.c
- * DESCRIPTION : Interrupt routines for the preset controller
- ************************************************************/
-
- /************************************************************
- *
- * NAM : rcv_msg
- * DESCRIPTION: called by the interrupt routine which is
- * invoked by uart interrupt bit.
- * COMPATIBLE LANGUAGES: <Assembler only>
- ************************************************************/
- #include "serial.h"
- #include "que.h"
-
- extern TRCSR;
- extern TDR;
- extern xstatus;
- extern struct g_queue xmit_que;
- extern struct g_queue in_que;
- extern RDR;
- rcv_msg()
-
- {
- unsigned char xstatus, temp;
- unsigned int remove, insert;
-
- xstatus = TRCSR;
- if (xstatus & RDRF) /* did we receive something? */
- {
- temp = RDR;
- remove = in_que.remove;
- insert = in_que. insert;
- if ((insert+1!=remove) && (insert-remove!=kqlength-1))
- { /* queue is full */
- in_que.que[insert++] = temp;
- if (insert == kqlength)
- insert = 0;
- in_que.insert = insert;
- in_que.empty = false;
- }
- }
- if (xstatus & TDRE)
- { /* do we have something to send? */
- if(xmit_que.empty == false)
- {
- remove = xmit_que.remove;
- TDR = xmit_que.que[remove++];
- if (remove == kqlength)
- remove = 0;
- xmit_que.remove = remove;
- if (remove == xmit_que.insert)
- xmit_que.empty = true;
- }
- else
- {
- /* turn off the interupt for transmit buffer empty and
- turn off the serial transmit buffer */
- TRCSR &= ~TIE;
- }
- }
- }
-
- /************************************************************
- *
- * NAME : counter_irq
- *
- * DESCRIPTION: called by the interrupt routine which is
- * invoked by global timer interrupt - increments
- * the global value of time and reads the keyboard.
- *************************************************************/
-
- extern enable_timer;
- extern globaltime;
- extern flashtime;
- extern dspy;
- extern disable_on;
- extern keyboard_port;
- extern P1DR;
- extern pad_read;
-
- counter_irq()
-
- {
- unsigned char i,line;
- static unsigned char keyport;
- unsigned int temp;
-
- /* clear the TOF bit in the timer control & status reg.
- * AND then clear the interupt mask in the condition code reg.
- */
- ;
- #asm
- XREF TCSR?
- LDD TCSR?
-
- CLI
- #endasm
-
- /* increment the global timer variable */
- if (enable_timer) globaltime += 1;
-
- /* increment the flash time */
- flashtime += 1;
-
- /* refresh the display */
- show(dspy);
-
- /* if (!disable_on) then read the keypad */
- if (!disable_on)
- {
- line = 0x8;
- for (i=0; i<4; i++)
- {
- P1DR = line;
- line >>= 1;
- keyport = P1DR;
- if (keyport & 0xf0)
- break;
- }
- if (i==0)
- keyboard_port = (keyport & 0xf0) 1;
- else if (i==1)
- keyboard_port = (keyport & 0xf0) 2;
- else if (i==2)
- keyboard_port = (keyport & 0xf0) 4;
- else if (i==3)
- keyboard_port = (keyport & 0xf0) 8;
- pad_read = true;
- }
- }
-
-
- Listing 9 (msg_out.c)
- /*****************************************************
- * NAME : message_out
- *
- * DESCRIPTION:
- * waits for a message in the message out queue,
- * when one appears it assembles it then sends it.
- *****************************************************/
-
-
- #include "que.h"
-
- extern struct g_queue out_que;
- unsigned char buf[32];
-
- void message_out()
- {
- unsigned char no_msg_out,length;
- int i;
-
- while (true)
- {
- length = remove_one(&out_que);
- buf[0] = length;
- for(i=1; i<=length; i++)
- buf[i] = remove_one(&out_que);
- format_msg();
- send_msg();
- }
- }
-
- /******************************************************
- * NAME: format_msg
- *
- * DESCRIPTION: takes a message in the buffer and
- * formats it for the serial port.
- *******************************************************/
-
- format_msg()
- {
- /* prepare a buffer for crc generation */
-
- /* now calculate the CRC */
-
- /* insert the crc bytes into the buffer */
-
- /* now add the DLE characters */
-
- /* add the stop flag */
-
- /* now check the CRC characters to see if we should add DLE's */
-
- /* finally - put in the length of total buffer */
-
- }
-
- **********************************************************************
- *
- * NAME : send_msg
- *
- * */
- send_msg()
- {
- /* send the first character to the serial port to kick off the
- serial transmission. turn on the interupts and let the interrupts
- finish the rest of the transmission */
-
- /* now turn on the serial transmit buffer */
-
-
- /* send the first character to the serial transmit buffer */
-
- /* enable the interrupt for serial transmit data register empty */
-
- /* wait for the last character to be completely transmitted */
- }
-
-
- Listing 10 (que_hand.c)
- /**************************************************
- * NAME : insert_one
- *
- * DESCRIPTION: insert a item into the item queue.
- * This queue works as a circular buffer with two
- * pointers, insert and delete. As a character is
- * inserted the insert pointer is incremented
- * modulo que length. In other words after each
- * increment the pointer is checked to see if it
- * equals the length of the que. If so, then it
- * is reset to zero. When the pointers are equal
- * then the que is full. The insert pointer is always
- * incremented first then the item is inserted.
- * The empty flag is always set false if a item
- * is inserted.
- ******************************************************/
-
- #include "que.h"
-
- insert_one(item, k_que)
- unsigned char item;
- struct g_queue *k_que;
- {
-
- unsigned int insert,remove;
-
- while (((k_que->insert)+1==k_que->remove) ½½
- ((k_que->insert)-(k_que->remove)==kqlength-1))
- suspend(); /* queue is full */
-
- set_irq();
- insert = k_que->insert;
- remove = k_que->remove;
- (*k_que).que[insert] = item;
- if (++insert == kqlength)
- insert = 0;
- k_que->insert = insert;
- k_que->empty = false;
- clear_irq();
- }
-
- /***********************************************
- * NAME : remove_one
- *
- * DESCRIPTION: remove a item from the item queue.
- * This queue works as a circular buffer with two
- * pointers insert, and delete. as a character is
- * removed the remove pointer is incremented modulo
- * que length. In other words after each increment
- * the pointer is checked to see if it equals the
-
- * length of the que. If so, then it is reset
- * to zero. When the pointers are equal then the
- * que is empty. And the que empty flag is set
- * true. The remove pointer is always pointing to
- * the next character to be removed. When the
- * remove pointer is equal to the insert pointer
- * - 1 then the queue is full
- *
- ***************************************************/
-
- remove_one(k_que)
- struct g_queue *k_que;
- {
-
- unsigned int insert,remove;
- unsigned char item;
-
- while (k_que->empty == true)
- suspend();
- set_irq();
- insert = k_que->insert;
- remove = k_que->remove;
- item = k_que->que[remove];
- if (++remove == kqlength)
- remove = 0;
- if ( remove == insert )
- k_que->empty = true;
-
- k_que->remove = remove;
- clear_irq();
- return item;
- }
-
-
- Listing 11 (pcon.h)
- /*************************************
- * Name : pcon.h
- * Description: data structures for
- * communication within the kernel
- *************************************/
-
- struct sale_status {
- unsigned char sale_flag;
- unsigned char digits[6];
- unsigned char enter_flag;
- };
-
- struct rev_status {
- unsigned char major;
- unsigned char minor;
- unsigned char date[6];
- };
-
- struct config_table {
- unsigned char jordan_round;
- unsigned char num_dec_pts_c;
- unsigned char num_dec_pts_v;
- unsigned char cash_vol_def;
- unsigned char c_or_v;
-
- unsigned char fill_dash;
- unsigned char lst_sig_dig_c;
- unsigned char lst_sig_dig_v;
- unsigned char preset_req;
- };
-
- /* data structures for serial comm mesages */
-
- struct sale_msg { /* almost the same as */
- unsigned char salef; /* sale_status except */
- unsigned char digits[3]; /* the digits are */
- unsigned char enterf; /* represented by BCD */
- }; /* instead of byte */
-
- struct rev_msg {
- unsigned char major;
- unsigned char minor;
- unsigned char date[3];
- };
-
-
- Listing 12 (serial.h)
- /*****************************************
- * Name: serial.h
- * Description: all info and data
- * structures for serial comm
- *****************************************/
-
- #define WU 0x01 /* wake up bit */
- #define TE 0x02 /* transmit enable */
- #define TIE 0x04 /* transmit interrupt enable */
- #define RE 0x08 /* receive enable */
- #define RIE 0x10 /* receive interrupt enable */
- #define TDRE 0x20 /* transmit data register empty */
- #define ORFE 0x40 /* overrun framing error */
- #define RDRF 0x80 /* receive data register full */
-
- #define SSO 0x01
- #define SS1 0x02
- #define CCO 0x04
- #define CC1 0x08
-
- #define test_mode CC1 ½ SSO /* RMCR = 00001001 */
- #define tr_test TE ½ RE /* TRCSR = 00001010 */
- #define normal_mmode RIE ½ RE ½ TE
- /* TRCSR = 00011010 */
-
-
- Listing 13 (que.h)
- /************************************
- * Name: que.h
- * Description: typedef for queues for
- * preset controller
- ************************************/
-
- #define kqlength 32
-
- struct g_queue {
- unsigned char empty;
-
- unsigned int insert;
- unsigned int remove;
- unsigned char que[kqlength];
- };
-
- typedef unsigned char message_buf[32];
-
-
- Listing 14 (main.c)
- /*******************************************
- * NAME : Main.c
- *
- * DESCRIPTION : Main controlling routine
- * for the preset controller.
- ******************************************/
-
- #include "pcon. h"
- #include "que.h"
- #include "serial.h"
-
- extern void key_msg();
- extern void message_out();
- extern void massage_in();
- extern void display();
- extern void diag();
-
- /* at 08 Timer Control and Status Register: */
- extern unsigned char TCSR;
- /* at 09:0A Counter: */
- extern unsigned char COUNTER;
- /* at 0B:0C Output Compare Register: */
- extern unsigned char OCR;
- /* at 0D:0E Input Capture Register: */
- extern unsigned char ICR;
- /* at 0F Port 3 Control and Status Register: */
- extern unsigned char P3CSR;
- /* rate and mode control register: */
- extern unsigned char RMCR;
- /* transmit/receive control register: */
- extern unsigned char TRCSR;
- /* receive data register: */
- extern unsigned char RDR;
- /* transmit data register: */
- extern unsigned char TDR;
-
- extern unsigned char P1DDR;
- extern unsigned char P1DR;
- extern unsigned char P2DDR;
- extern unsigned char P2DR;
-
- /* for debugging */
- extern unsigned char over, received;
-
- struct sale_status key_buf;
- struct rev_status rev_stat;
- struct config_table config;
- struct g_queue key_que;
- struct g_queue in_que;
- struct g_queue out_que;
-
- struct g_queue xmit_que;
-
- int globaltime;
- in flashtime;
- unsigned char disable_on;
- unsigned char test_stat;
- unsigned char nozzle_up;
- extern unsigned char enable_timer;
-
- /* date of release */
- const char date[] = "022290";
- /* major revision number */
- define revis0 0
- /* minor revision number */
- define revis1 6
-
- /**************************************************
- * NAME : main
- *
- * DESCRIPTION: call all the initialization of
- * hardware and task setup then
- * loops forever and calls each task
- * in a round-robin fashion. Each
- * task will run until it suspends
- * itself then return to main for
- * the next task in the list.
- ***************************************************/
-
- void main()
- {
-
- initsystem();
- initstructures();
- init_task(0,&display);
- init_task(1,&message_in);
- init_task(2,&message_out);
- init_task(3,&key_msg);
- clear_irq();
- while(true)
- {
- continue_task(0);
- continue_task(1);
- continue_task(2);
- continue_task(3);
- }
- }
-
- /****************************************************
- * NAME : initstructures
- *
- * DESCRIPTION: init the data structures for the
- * firmware sets up all pointers and
- * what ever else need doing
- ****************************************************/
-
- initstructures ()
- {
- int i;
-
-
- /* init the key queue */
- key_que. insert= 0;
- key_que.remove = 0;
- key_que.empty = true;
-
- /* init the in message que */
- in_que.insert = 0;
- in_que.remove = 0;
- in_que.empty = true;
-
- /* init the out message que */
- out_que.insert = 0;
- out_que.remove = 0;
- out_que.empty = true;
-
- /* init the Transmit que */
- xmit_que.insert = 0;
- xmit_que.remove = 0;
- xmit_que.empty = true;
-
- /* init the all other data as needed */
- }
-
- /*****************************************************
- * NAME: initsystem
- *
- * DESCRIPTION:
- * Init the counter timer chip for continous
- * operation and to interupt at overflow.
- * Init the serial port for receive and send and
- * to generate an interrupt whenever the transmit
- * buffer is empty and the receive buffer is full.
- ****************************************************/
-
- #define ETOI 0x04
-
- initsystem()
- {
-
- /* set up the rate and mode control
-
- for internal clock and 9600 */
- RMCR = CC0 ½ SS0;
-
- /* set up the serial port to transmit and receive */
- TRCSR = normal_mode;
-
- /* set up the free running counter
- to interupt at overflow */
- TCSR = ETOI;
-
- /* set up port 1 bits 0-3 as write only
- and bits 4-7 as read only */
- P1DDR = 0x0f;
-
- /* set up port 2 bit 0 as a write bit
- and turn off the serial transmit buffer */
- P2DDR = 0x01;
- P2DR = 0xFF;
-
- }
-
- /***************************************************
- * NAME : set_irq, clear_irq
- *
- * DESCRIPTION: two routines that set the
- * interupt mask and clear the
- * interrupt mask in the condition
- * code register of the 6801
- **************************************************/
-
- /* routine to set the interupt mask */
-
- set_irq()
- {
-
- ;
- #asm
- sei
- #endasm
- }
-
- /* routine to clear the interupt mask */
-
- clear_irq()
- {
- ;
- #asm
- cli
- #endasm
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Real-Time Data Acquisition
-
-
- David Fugelso and Mike Michnovicz
-
-
- David Fugelso has a B.S. in computer science from the University of New Mexico
- and has seven years of experience in software engineering. Mike Michnovicz has
- an M.S. in electrical engineering from the University of New Mexico and has
- seven years of experience in systems integration and embedded systems. The
- authors may be reached at Titan/Spectron P.O. Box 9254, Albuquerque NM,
- 87119-9254 or via e-mail on Compuserve at 72460,741.
-
-
- Titan/Spectron has developed a real-time system called DIAL, for DIfferential
- Absorption Lidar. It is used for the remote detection of hydrocarbon gasses
- such as methane, ethane, and propane. The DIAL technique measures the
- atmospheric absorption of a laser beam to sense the presence of gasses. It
- does so by measuring and comparing the return signals of laser pulses at two
- different wavelengths to locate specific gasses. This system makes six lidar
- measurements at a continuous rate of 10Hz. The program collects lidar
- measurements, writes them to disk, performs the DIAL calculations, and
- displays the results. The first two of these functions have real-time
- requirements, but the calculation and display function does not since data is
- saved and can be processed later.
- DIAL is based on a real-time UNIX system that uses System V inter-process
- communication facilities for synchronizing concurrent tasks and sharing data.
- The algorithm has one producer and two consumer tasks. It uses a bounded
- buffer technique implemented with System V message queues and shared memory.
- The system acquires data from six digitizer channels, each sampling at 20
- million samples per second over a period of 20 microseconds. The digitizer has
- a resolution of 12 bits. Two bytes store and transfer each sample, so the host
- computer must read, write, and process 4,800 bytes (400 x 2 x 6) per pulse.
- The 4,800 bytes of data collected from the six lidar measurements is referred
- to as a frame. At 10Hz, the data acquisition system must have a minimum
- continuous throughput of 48 Kbytes/sec.
- The host machine for this system is a 386-based computer with an AT bus
- running LynxOS, a real-time UNIX operating system, from Lynx Real-Time
- Systems. (See the sidebar on real-time operating systems.) The system includes
- a 370Mb hard drive connected to the AT via a SCSI interface. It transfers from
- the digitizer to the host via an IEEE-488 bus (also known as a GPIB). The
- system's IEEE- 488 controller uses direct memory access (DMA) to place data
- into host memory.
-
-
- Algorithm Description
-
-
- Data acquisition work is split among three tasks: one producer task and two
- consumer tasks. A fourth task, the task synchronization module (TSM),
- synchronizes the activities of the working tasks. The producer task reads data
- over the IEEE-488 from the digitizer. The first consumer task writes data to
- disk; the second performs the DIAL calculation and graphically displays the
- data. The read task is the most critical; if it misses a trigger, digitizer
- data will be lost. The write task is less critical than the read task since
- frame buffers can be written at any time; however, the write task cannot get
- systematically behind. The graphics task, on the other hand, may lag behind
- real-time and is allowed to miss data.
- After receiving an external trigger event, the TSM task initiates the data
- acquisition process by locating an unused buffer and sending the buffer to the
- producer task. The producer task reads the data and sends the buffer to the
- first consumer task, which writes the data to disk and sends the buffer to the
- second consumer task. When that task is finished, it releases the buffer.
- Tasks pass buffers via four System V message queues: the available queue, the
- read queue, the write queue, and the graph queue. The actual buffers are not
- copied among the tasks. Rather, the tasks pass messages containing indexes to
- buffers located in shared memory. The TSM task creates these messages, each
- containing an index or "key" for a buffer, and places them in the proper
- queue. An index can be thought of as a key because each message is unique and
- corresponds to exactly one buffer. This message queue mechanism provides
- mutual exclusion among accesses to the shared memory and also provides task
- synchronization.
-
-
- Implementation
-
-
- The short program in Listing 1 starts the four data acquisition tasks. In the
- DIAL Lidar program, this task contains the user interface. The listing
- illustrates how the UNIX fork() call starts separate tasks. fork() makes an
- identical copy of the calling program with a new process ID. fork() returns a
- zero to the new task that is the child process, and returns the process ID of
- the child to the parent process.
- Listing 2 is the header file included by the four data acquisition tasks. The
- header file defines a semaphore, message queue, shared memory indexes (keys),
- the message structure, frame buffer size, number of buffers, the priorities of
- the tasks, and other miscellaneous values. The keys for semaphores, message
- queues, and shared memory allow the separate tasks to access the same system
- resources.
- The message structure contains two elements. The operating system requires and
- uses a message type field to differentiate between types of messages. Because
- all of the messages in this program are similar, type is not used. The second
- element is the frame buffer index or key to a contiguous segment of shared
- memory.
- Tasks run at different priorities. The TSM task has the highest priority since
- it must execute when an external event arrives. The read task has the second
- highest priority since the data must be acquired in a specific time frame. The
- write task has the next highest priority since the write to disk is critical
- but finite delays are acceptable. The lowest priority is assigned to the graph
- task. It essentially uses whatever CPU time is left over from the other tasks.
- Listing 3 is the TSM task, lidar_acq.c. As noted earlier, the TSM task
- synchronizes the activities of the other tasks. After setting its own priority
- and performing some initialization, the task creates unique messages for each
- frame buffer and places each message in the available queue.
- The TSM task then waits for the other tasks to finish loading and
- initializing. This is accomplished by three waits for the "alive" semaphore.
- These waits are necessary because the TSM task starts the external data
- acquisition process. If the other tasks are not ready, the acquisition process
- will break down. LynxOS provides an alternative form of the System V
- implementation of the semaphore, which the program uses here. sem_get() works
- similar to the System V semget() but uses a string key instead of an integer
- key. sem_signal() and sem_wait() replace the complicated System V function
- semop().
- Once the other processes have checked in, the TSM task writes to the trigger
- device the number of 400 micro-second cycles between triggers (count) and the
- number of triggers to generate (int_count). The trigger device is a custom
- device with an external clock developed to handle the intricate triggering of
- the lasers. (See the sidebar on developing device drivers for real-time UNIX.)
- The task then waits for the external trigger by reading the trigger "file."
- After receiving a trigger signal, the TSM task looks for a free buffer on the
- available queue through the msgrcv() system call. By making the call with the
- flag IPC_NOWAIT, the program instructs msgrcv() to return immediately whether
- a message is in the queue or not. If a buffer is available, the program places
- it into the read queue. Otherwise, the TSM task must free a buffer from
- another task. Since the calculate and graph process is not a critical part of
- the system, the program places all buffers in the graph message queue also in
- the available queue (using msgrcv() with the IPC_NOWAIT flag in a loop until
- the graph queue is empty). If still no buffers are available, the program has
- encountered a fatal error. (It has missed data.)
- After receiving all external events, the TSM task terminates the data
- acquisition session by placing a quit message on the read queue. The task then
- waits for messages for all of the frame buffers to be placed on the available
- queue, followed by the quit message. If then deletes all the semaphores,
- message queues, and shared memory resources. This allows the work tasks to
- complete any remaining processing.
- Listing 4 presents the producer task, lidar_read.c. This task synchronizes
- activities through the system call msgrcv() acting on the read message queue.
- It sends the parameter NOFLAGS to the routine, causing the task to pause until
- a message is placed in the read queue. Once the system has acquired data and
- placed it in shared memory, the task places a message in the write message
- queue.
- Not shown here is the code that initializes, reads, and closes the IEEE-488
- device. It was omitted partly to save space in this presentation. Digitizer
- device commands also vary among manufacturers. The code is somewhat messy
- because manufacturers support several types of digitizers.
- Listing 5 shows the first consumer task, lidar_write.c. The task's structure
- is similar to that of the read task. The task waits for a message to be placed
- in the write queue. After receiving the message, the task writes a frame
- buffer and places the message in the graph message queue.
- LynxOS provides a system service that allows the use of contiguous disk files.
- Using contiguous disk files speeds up I/O because the disk head does not have
- to move as much. Moreover, the system knows to bypass the disk cache. The
- program creates such a file with the mkcontig() system call, which requires a
- maximum file lenghth parameter. The program then opens the file and access it
- using standard system calls. A contiguous file imposes one additional
- constraint - data must be written in 512-byte chunks. Although each frame
- contains only 4,800 bytes, the program thus writes 5,120 bytes (ten 512-byte
- blocks) to disk. In the actual application, this extra 320 bytes is used to
- store ancillary information about each frame, such as pulse energies and
- absorption coefficients.
- Listing 6 shows the second consumer, lidar_graph.c. Again, the code is
- structured the same as the two preceding tasks. When the task is finished
- processing a buffer, it places the buffer on the available message queue.
- The second consumer performs DIAL calculations. It can display up to six
- graphs using X Window library calls. For brevity, the listing omits the
- calculation and graphics code. In its place is a CPU delay function, which
- gives an example of using UNIX signals.
-
-
- Performance
-
-
- Running at 10Hz, the system has 100 msec to acquire the data for each pulse.
- Of this, the producer task takes between 20 and 40 msec (depending on the
- digitizer) of real time to transfer 4,800 bytes of data over the IEEE-488 bus.
- This time includes the time it takes the digitizer to acquire a waveform and
- the overhead to initiate the transfer. Since the transfer uses DMA, CPU usage
- is much smaller (less than 10 msec).
- The first consumer task completes the write to disk in 10 msec real time and
- uses less than 0.7 msec of CPU time. (Without the use of contiguous disk
- files, the write time triples.)
- The second consumer task can take between 0 and 200 mseconds depending on
- which graphics option is selected. Some processing options cannot be performed
- in real time. The display will lag behind data acquisition, causing the
- program to periodically dump the graphics message queue, resulting in time
- gaps in the display data. The number of buffers allocated affects both the
- length of the gaps and the time between gaps. Using ten buffers and assuming a
- calculation and display time of 100 mseconds/buffer, you can see that the
- graph queue will be dumped every 5.8 seconds:
- Display Time /
- (Total CPU time/cycle - 0.1 s)
- * Nbuffers * 0.1 s
- When the program dumps the graph queue, the gap length will be 900 msec,
- because the program removes nine frame buffers (Nbuffers - 1) from the graph
- queue.
- The other factors that have an impact system performance are the control
- statements, context switches, and message queue overhead. Of these, only the
- message queue overhead has significant impact. The time to send and receive a
- message is approximately 0.3 msec, so with four transfers per frame, 1.2 msec
- are used for synchronization.
-
-
- Conclusion
-
-
-
- I have presented a flexible and easy-to-implement solution for the
- producer/two consumer data acquisition problem. The LynxOS operating system
- provides the real-time environment and the facilities to easily develop
- real-time programs.
- References
- "Real-Time Data Acquisition," Mike Bunnel and Mitch Bunnel, Dr. Dobbs Journal,
- June 1989 pp. 36-44.
- "Unix Interprocess Communication," William J. Freda, The C Users Journal,
- November 1990, Vol. 8 No. 11, pp. 49-61.
- Concurrent Programming, Fundamental Techniques for Real-Time and Parallel
- Software Design, Tom Axford, Wiley & Sons, West Sussex, England.
- What Makes A Real-Time Operating System?
- Two basic areas separate real-time operating systems from non-real time
- operating systems -- task scheduling and fast response to external events. A
- real-time program requires an operating system that guarantees a maximum
- response time to external events. The key to fast servicing of interrupts is
- the pre-emption of currently executing tasks, including those tasks that are
- active within system calls. UNIX allows the system kernel to be interrruped,
- but it cannot be pre-empted while performing a system call. MS-DOS BIOS calls
- can be interrupted, but the system code is not reentrant. (Two concurrent
- tasks cannot make the same system call). Even in real-time systems, response
- time is hard to measure. Instructions vary in execution time and portions of
- the operating system may lock out interrupts during critical sections.
- The other basic difference is task scheduling. UNIX uses a time-sharing task
- scheduler that changes priorities of tasks so that system resources are shared
- fairly among users. A real-time task scheduler does not change task
- priorities. Task priorities are set by the user and remain set, so an infinite
- loop running at higher priority than a shell or login task cannot be
- interrupted and will never end.
- A real-time system also requires a set of system facilities for efficient
- inter-process communication and task control. The operating system must
- provide facilities for creating and deleting tasks, setting priorities,
- synchronizing, and sharing information.
- The DIAL Lidar project uses the LynxOS operating system from Lynx Real-Time
- Systems. The LynxOS system is based on UNIX but was completely rewritten to
- support real-time programming. The time-sharing task scheduler was replaced
- with a real-time scheduler, and the kernel was rewritten to allow pre-emption
- and to be reentrant. System V communication facilities provide inter-process
- communication, including semaphores, shared memory, message queues, signals,
- and pipes.
- Writing Device Drivers In LynxOS
- Device drivers carry enough mystique that some programmers shy away from the
- subject. But with LynxOS, both beginners and experts can write device drivers.
- LynxOS makes installation especially easy -- programmers can install drivers
- into the system without rebuilding the system kernel, which in turn allows
- online debugging.
- One of the DIAL Lidar project goals was to minimize development time by
- choosing a system that included all of the necessary device drivers. LynxOS
- was chosen partially because it satisfied that requirement. However, as the
- project progressed, the need to control a custom peripheral device presented
- an opportunity to build a custom device driver. We needed to design and build
- a digital circuit that could produce the timing signals necessary to operate a
- Neodymium YAG laser externally and to trigger a digitizing oscilloscope. The
- circuit needed to be programmable with respect both to delays between the
- various laser control pulses and to the repetition rate of the laser.
- We chose to base the circuit on the familiar Intel 8253 timer chip, using only
- one of the 8253's three times. Its output, the "master" timing pulse, is
- routed to the custom digital circuitry that generates the various timing
- pulses for the laser and the digitizer. It is also connected to one of the bus
- interrupt lines. The master timing pulse triggers a chain of events external
- to the system computer. It also initiates the processing needed to capture and
- process the lidar return data.
- The structure of this simple device driver resembles that of a standard UNIX
- driver. It has a top half consisting of the standard open, close, read, write,
- and ioctl calls. By using conventional UNIX I/O system calls, you can
- interface with the timing circuit hardware. The driver also has a bottom half
- that quickly responds to an interrupt from the timer. The sole function of the
- handler is the generation of a signal used to synchronize the lidar program's
- multiple tasks. In this driver, the coupling between the driver's top and
- bottom halves is very loose. No data needs to be input or output within the
- interrupt handler, nor does the top half of the driver process any input or
- output. It merely reads and writes the 8253's status and control registers.
- Testing the card and driver software was also easy. We connected an
- oscilloscope to the timer output, to verify that we could control the
- operation of the 8253 using the appropriate driver calls. We checked the
- interrupt handler by starting up a task that writes to the screen after
- receiving a signal, and then signaling the task from the handler.
-
- Listing 1 (start.c)
- Listing 1 (start.c)
- /* ---
- Simple routine to start all the processes
- for the lidar program. The program starts
- each routine with a fork call, and then
- terminates.
- --- */
-
- #include <stdio.h>
-
- /* --- process names --- */
- static char *tasks[] = { "lidar_acq",
- "lidar_graph", "lidar_write",
- "lidar_read", NULL};
- main ()
- {
- int i = 0;
- while (tasks[i] != NULL) {
- if (fork()) /* child, start task */
- execve (tasks[i], NULL, NULL);
- i++;
- }
- }
-
-
- Listing 2 (lidar.h)
- Listing 2 (lidar.h)
- /* ---
- Header for lidar tasks. This header includes
- the IDs for the message queues and shared memory,
- shared structures, and shared definitions.
- --- */
-
- #include <file.h>
- #include <time.h>
- #include <types.h>
- #include <stat.h>
- #include <resource.h>
- #include <sys/ipc.h>
-
- #include <sys/msg.h>
- #include <sys/sem.h>
- #include <sys/shm.h>
- #include <sys/oscalls.h>
- #include <tclock.h>
-
- /* --- queue keys --- */
- #define AVAILABLE_Q 1
- #define GRAPHICS_Q 2
- #define READ_Q 3
- #define WRITE_Q 4
- /* --- shared memory key --- */
- #define FB_KEY 10
-
- /* --- semaphore keys --- */
- #define ALIVE_KEY "alive"
-
- /* --- message structure containing frame buffer
- index (key) for shared memory access --- */
- typedef struct message_rec {
- long type; /* required */
- int key; /* index into shared memory */
- } message;
-
- /* -- defines for messages -- */
- #define MSG_SIZE 4
-
- /* --- frame buffer number and size --- */
- #define N_FRAMES 10
- #define FRAME_SIZE 2560
- #define WRITE_SIZE 2560
- #define READ_SIZE 2070
-
- /* --- number of runs to make for sample program --- */
- #define N_RUNS 100
-
- /* --- priorities for each process --- */
- #define LIDAR_ACQ 19
- #define LIDAR_READ 18
- #define LIDAR_WRITE 17
- #define LIDAR_GRAPH 16
-
- /* --- useful defines --- */
- #define TRUE 1
- #define FALSE 0
- #define QUIT -1
- #define NOFLAGS 0
- #define ANYTYPE 0
-
-
- Listing 3 (lidar_acq.c)
- /* ---
- lidar_acq task. This is the task synchronization task.
- This program initialiazes shared memory and the message
- queues for the real-time portion of the lidar program.
- --- */
-
- #include "lidar.h"
-
-
- main()
- {
- int i, j, trigger_fd, alive_sem, shm_seg_id;
- int avail_qid, graph_qid, read_qid, write_qid;
- message mptr;
- trigger_clock cycles;
-
- /* --- set routine priority --- */
- setpriority (PRIO_PROCESS, 0, LIDAR_ACQ);
-
- /* --- open task alive semaphore --- */
- alive_sem = sem_get(ALIVE_KEY, 0);
-
- /* --- open message queues --- */
- avail_qid = msgget (AVAILABLE_Q, 0666 IPC_CREAT);
- graph_qid = msgget (GRAPHICS_Q, 0666 IPC CREAT);
- read_qid = msgget (READ_Q, 0666 IPC_CREAT);
- write_qid = msgget (WRITE_Q, 0666 IPC_CREAT);
-
- /* --- create shared memory segment ids --- */
- shm_seg_id = shmget (FB_KEY, FRAME_SIZE * N_FRAMES,
- 0666 IPC_CREAT);
-
- /* --- create messages for shared memory
- indexing and place them in the available
- message queue --- */
- for (i = 0; i < N_FRAMES; i++) {
- mptr.key = i;
- if (msgsnd (avail_qid, &mptr, MSG_SIZE, 0) < 0) {
- printf ("Queue problem.\n");
- exit (1);
- }
- }
-
- /* --- wait for the read, write, and graph
- tasks to check in --- */
- sem_wait (alive_sem);
- sem_wait (alive_sem);
- sem_wait (alive_sem);
-
- /* --- open trigger timing device and set to
- 10 Hz for N_RUNS pulses --- */
- trigger_fd = open("/dev/tclock", O_RDWR);
- cycles.count = 250; /* 250 cycles = .1 sec */
- cycles.int_count = N_RUNS; /* number of pulses */
- i = write (trigger_fd, &cycles, 4);
-
- /* --- real time data acquisition loop --- */
- for (i = 0; i < N_RUNS; i++) {
- /* --- wait for trigger --- */
- if (read (trigger_fd, &cycles, 2)) {
- printf ("Missed a pulse. Fatal Error\n");
- break;
- }
-
- /* --- get a free frame buffer --- */
- j = msgrcv (avail_qid, &mptr, MSG_SIZE, 0, IPC_NOWAIT);
-
- /* --- if there aren't any available buffers --- */
-
- if (j < 0) {
- /* --- remove all of the frame buffers
- from the graph message queue and
- place them in the available queue --- */
- while (msgrcv(graph_qid, &mptr, MSG_SIZE,
- ANYTYPE, IPC_NOWAIT) >= 0)
- msgsnd (avail_qid, &mptr, MSG_SIZE, 0);
- /* --- if there still are no buffers,
- fatal error has occured --- */
- if (msgrcv (avail_qid, &mptr, MSG_SIZE,
- ANYTYPE, IPC_NOWAIT) == -1) {
- printf ("Fatal queue problem. Terminating\n");
- break;
- }
- }
-
- /* --- start the acquisition process --- */
- msgsnd (read_qid, &mptr, MSG_SIZE, IPC_NOWAIT);
- }
-
- /* --- terminate data acquisition --- */
- close(trigger_fd);
-
- /* --- signal other processes to quit --- */
- mptr.key = QUIT;
- msgsnd (read_qid, &mptr, MSG_SIZE, NOFLAGS);
-
- /* --- wait for all messages to be placed
- on available Q and retire --- */
- for (i = 0; i < N_FRAMES; i++)
- msgrcv(avail_qid, &mptr, MSG_SIZE, ANYTYPE, NOFLAGS);
-
- /* --- and the terminate message --- */
- msgrcv (avail_qid, &mptr, MSG_SIZE, ANYTYPE, NOFLAGS);
-
- /* --- delete queues --- */
- msgctl (avail_qid, IPC_RMID, 0);
- msgctl (read_qid, IPC_RMID, 0);
- msgctl (write_qid, IPC_RMID, 0);
- msgctl (graph_qid, IPC_RMID, 0);
-
- /* --- release semaphore --- */
- sem_delete (ALIVE_KEY);
-
- /* --- delete shared memory segments --- */
- shmctl (shm_seg_id, IPC_RMID, 0);
- }
-
-
- Listing 4 (lidar_read.c)
- /* ---
- lidar_read task. This task initializes shared
- memory, waits for a message, reads a frame
- buffer from the digitizer, and sends a message
- to the write task when the frame buffer is full.
- --- */
-
- #include "lidar.h"
-
-
- main()
- {
- int ieee_488;
- int read_qid, write_qid;
- int alive_sem;
- int terminate = FALSE;
- char *fb_shm;
- message mptr;
-
- /* --- set process prioprity --- */
- setpriority (PRIO_PROCESS, 0, LIDAR_READ);
-
- /* --- open message queues --- */
- read_qid = msgget (READ_Q, 0666 IPC_CREAT);
- write_qid = msgget (WRITE_Q, 0666 IPC_CREAT);
-
- /* --- get semaphore --- */
- alive_sem = sem_get (ALIVE_KEY, 0);
-
- /* --- create shared memory frame buffers --- */
- fb_shm = shmat(shmget (FB_KEY, FRAME_SIZE*N_FRAMES,
- 0666IPC_CREAT), 0, 0);
-
- /* --- open ieee file and set up digitizer --- */
- ieee_488 = init_digitizer ();
-
- /* --- tell the world we're open for business --- */
- sem_signal (alive_sem);
-
- /* --- while lidar data acquisition program running --- */
- while (!terminate) {
- /* --- wait for a message to start read --- */
- msgrcv (read_qid, &mptr, MSG_SIZE, ANYTYPE, NOFLAGS);
-
- /* --- check for quit --- */
- if (mptr.key == QUIT)
- terminate = 1;
- else
- read_digitizer (ieee_488, fb_shm +
- mptr.key * FRAME_SIZE);
-
- /* --- send the message on to the write task --- */
- msgsnd (write_qid, &mptr, MSG_SIZE, IPC_NOWAIT);
- }
-
- /* --- close digitizer file --- */
- close_digitizer (ieee_488);
-
- /* --- release memory --- */
- shmdt (fb_shm);
- }
-
-
- Listing 5 (lidar_write.c)
- /* ---
- lidar_write task. This task waits for a
- message from the read routing. When a message
- arrives, a frame buffer is written to disk.
- After the write, the message is enqueued
-
- onto the graph queue.
- --- */
-
- #include "lidar.h"
-
- main()
- {
- int raw_fd, alive_sem;
- int terminate = 0;
- int graph_qid, write_qid;
- char *fb_shm;
- message mptr;
-
- /* --- set process priority --- */
- setpriority (PRIO_PROCESS, 0, LIDAR_WRITE);
-
- /* --- open message queues --- */
- graph_qid = msgget (GRAPHICS_Q, 0666 IPC_CREAT);
- write_qid = msgget (WRITE_Q, 0666 IPC_CREAT);
-
- /* --- get alive semaphore --- */
- alive_sem = sem_get (ALIVE_KEY, 0);
-
- /* -- create shared memory buffer -- */
- fb_shm = shmat (shmget (FB_KEY, FRAME_SIZE * N_FRAMES,
- 0666 IPC_CREAT), 0, 0);
-
- /* --- create a contiguous file --- */
- mkcontig ("test.out", S_IWRITE, (long)N_RUNS *
- (long)FRAME_SIZE);
-
- /* --- open raw, write file --- */
- if ((raw_fd = open("test.out", O_WRONLYO_CREAT)) == -1) {
- printf("Unable to open raw file: lidar_write.\n");
- exit (1);
- }
-
- /* --- tell the world we're open for business --- */
- sem_signal (alive_sem);
-
- /* --- main write loop --- */
- while (!terminate) {
- /* --- wait for a buffer ---- */
- msgrcv (write_qid, &mptr, MSG_SIZE, ANYTYPE, NOFLAGS);
-
- /* --- check for terminate --- */
- if (mptr.key == QUIT)
- terminate = 1;
- else
- write (raw_fd, fb_shm + mptr.key * FRAME_SIZE,
- WRITE_SIZE);
-
- /* --- pass frame buffer to graphics/processing
- task --- */
- msgsnd (graph_qid, &mptr, MSG_SIZE, IPC_NOWAIT);
- }
-
- /* -- release memory -- */
- shmdt (fb_shm);
-
-
- /* --- close device --- */
- close (raw_fd);
- }
-
- Listing 6 (lidar_graph.c)
- /* ---
- lidar_graph task. This program processes
- the raw frame buffer. Frame buffers are received
- through the graph q, when processing is finished,
- the frame buffer is placed on the available queue.
-
- For the sample program, processing and graphing
- are replaced by a call to delay which uses
- DELAY_TIME cpu time.
- --- */
-
- #include <stdio.h>
- #include <signal.h>
- #include "lidar.h"
-
- #define DELAY_TIME 0.3
-
- main ()
- {
- int terminate = 0;
- int graph_qid, avail_qid;
- char *fb_shm;
- int alive_sem;
- message mptr;
-
- /* --- set process priority --- */
- setpriority (PRIO_PROCESS, 0, LIDAR_GRAPH);
-
- /* --- open message queues --- */
- graph_qid = msgget (GRAPHICS_Q, 0666 IPC_CREAT);
- avail_qid = msgget (AVAILABLE_Q, 0666 IPC_CREAT);
-
- /* --- create alive semaphore --- */
- alive_sem = sem_get (ALIVE_KEY, 0);
-
- /* -- create shared memory buffers -- */
- fb_shm = shmat (shmget (FB_KEY, FRAME_SIZE * N_FRAMES,
- 0666 IPC_CREAT), 0, 0);
-
- /* --- tell the world we're open for business --- */
- sem_signal (alive_sem);
-
- /* --- while lidar program running --- */
- while (!terminate) {
- /* --- wait for the next buffer --- */
- msgrcv (graph_qid, &mptr, MSG_SIZE, ANYTYPE, NOFLAGS);
- /* --- check for termination --- */
- if (mptr.key == QUIT)
- terminate =1;
- else
- delay (DELAY_TIME);
-
- /* --- place buffer on the availaible queue --- */
-
- msgsnd (avail_qid, &mptr, MSG_SIZE, IPC_NOWAIT);
- }
-
- /* -- release memory -- */
- shmdt (fb_shm);
- }
-
- int cpu_hog_func ();
- int take_cpu_time = 1;
- #define SETTIMER(t,p) \
- t.it_value.tv_sec = (int)delta; \
- t.it_value.tv_usec = (p - (int)p) * 1000000; \
- setitimer (ITIMER_PROF, &t, 0);
-
- /* --- delay and grab cpu time --- */
- delay (delta)
- double delta;
- {
- struct itimerval t;
-
- /* --- signal handler for profiler interrupt --- */
- signal (SIGPROF, cpu_hog_func);
-
- /* --- turn on timer --- */
- SETTIMER(t,delta);
-
- /* --- loop until stop condition --- */
- while (take_cpu_time)
- /* do nothing */;
-
- /* --- reset loop condition --- */
- take_cpu_time++;
-
- /* --- turn off timer --- */
- SETTIMER(t,0);
- }
-
- cpu_hog_func ()
- {
- take_cpu_time--;
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Portable C For The 8051 Microcontroller
-
-
- Frank van den Berg
-
-
- This article is not available in electronic form.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Building Embedded Systems With Turbo C
-
-
- Pat Villani
-
-
- Pat Villani received his B.S.E.E. from Polytechnic Institute of Brooklyn and
- his M.S.E.E. from Polytechnic Institute of New York. Pat has developed
- applications ranging from avionics and guidance to computer peripherals. He is
- president of Network Software Systems, Ltd., which specializes in C
- applications and embedded control systems. Readers may reach him at (908)
- 206-0320.
-
-
- Embedded systems have traditionally represented a specialized area of computer
- science and electrical engineering, an application where both disciplines
- merge. System design often requires tradeoffs between programming convenience
- and system cost. Quite often, code produced for embedded systems is created by
- both software and hardware engineers.
- The first microprocessors used in embedded systems implemented a lot of code
- in assembly langauage because of the type of software development packages
- available for these processors. These packages were often expensive and
- required the manufacturer's development system or a large machine to build
- code. Only large corporations could afford these programming platforms. As
- microprocessor capabilities increased, manufacturers introduced programming
- langauges geared toward embedded systems. These new languages were limited to
- the same platforms, so the trend of limited embedded system development
- continued.
- Concurrently, great strides were made in desktop computers. These computers,
- based on the same microprocessors that were used in embedded systems, were
- capable of running applications and small software development packages that
- targeted these machines. Further developments in desktop computers enabled the
- developers of these packages to include more advanced features. The result:
- powerful langauges such as the Borland Turbo languages and Microsoft languages
- that include debuggers and integrated development environments that allow
- rapid development of applications.
- Today, low-cost clone motherboards, hybrids, and single chip computers based
- on XT and AT architectures exist. When combined with low-cost peripherals,
- these motherboards and devices present a unique opportunity to system
- developers. Low-cost and small-volume controllers, data loggers, and
- specialized processors are feasible. Adapting langauges such as Turbo C to
- produce code for these environments makes these designs even more affordable.
-
-
- Basic Principles
-
-
- Compilers such as Borland Turbo C generate efficient code that is independent
- of any operating system. What ties the compiler to an operating system or any
- environment is the system startup code and the libraries that are linked to
- produce the executable object file. Replacing these files with new files
- specific to embedded systems allows the code to work in an embedded system.
- The first file you must replace is the startup file. Turbo-C uses one of the
- files COx. OBJ (where x is T, S, L, etc., depending on the memory model used)
- found in \TC\LIB. This file transforms the unique parameters of MS-DOS passed
- at startup into the conventional C environment argc, argv, envp, and errno.
- The file also sets up the stacks and aligns the segments of memory.
- The next file that you must replace is the corresponding flavor of
- \TC\LIB\Cx.LIB. This file contains all the operating system calls, such as
- read and write. It also contains library functions such as strcpy and tolower.
- Our approach is to replace this file with a new library emulating these calls.
-
-
- Design Advantages
-
-
- Rapid development is a key factor in today's market. This approach helps the
- system developer achieve this goal by reducing development time. The project
- is developed using the Turbo C integrated environment or Turbo Debugger. When
- the developer is satisfied with the code's functionality, the code is
- recompiled and linked using make files and the command line environment. A
- commercial or public domain locater is then used to create an EPROM for the
- target system.
- The project is quickly debugged in a convenient environment in which code
- changes are rapidly made and revision control is maintained. Using the Turbo
- Debugger, nearly all C code and assembly code can be debugged, registers
- examined, breakpoints set, etc., without the need for a costly in-circuit
- emulator. If a suitable ROM monitor is used during the final debug cycle, an
- in-circuit emulator may not be needed at all.
- Since the code is transported from an MS-DOS environment to a target system,
- the functional code can be easily transported to a different 80x86-based
- target system by simply changing the device drivers and library I/O files.
- This built-in hardware independence aids the developer in writing reusable
- code, further reducing development time in future projects.
-
-
- Example Project
-
-
- The development of a simple 8086 ROM monitor illustrates this design approach
- (Listing 1). The monitor, called mon86, performs two functions:
- examine and change memory
- go to specified address
- It uses a simple table-driven executive that can be extended by adding new
- table entries and suitable functions to execute these new commands. An error
- function terminates the table.
- The monitor uses only Turbo C calls that comply with both SVID and POSIX
- definitions, improving portability. DOS-unique system calls, such as INT21,
- etc., are not used. Using them would complicate the code by requiring the use
- of conditional preprocessor directives to selectively include the non-portable
- sections of code. It also complicates library coding, since it requires that
- DOS code also be emulated.
- For this project, the C library is broken into two libraries: a system call
- emulation and a portable C library. The example project uses simple I/O calls
- such as read and write. The functions open, close, and ioctl are also
- implemented for completeness. Restricting I/O function calls to this small set
- reduces the work required to implement the emulation package.
- The monitor C library calls are limited to simple getc and putc. These calls
- are implemented as links to Turbo C calls _fputc and _fgetc, which allow for
- full use of Turbo C's <stdio.h>, further enhancing portability. Also, simple
- formatted output is required for memory display. To achieve this with an eye
- on portability, the library provides an integer-only printf. The choice of a
- limited printf has two justifications:
- it is simpler to implement, because it does not require knowledge of compiler
- floating point representation
- limiting its use to int and long guarantees that the floating-point library is
- not used, yielding smaller ROM code.
- A machine library, called libm. lib, is also needed. This library is a
- collection of all subroutines required to supplment C operations not supported
- by the processor itself, such as long divides, multiplies, etc. These
- subroutines are naturally operating system independent. It is best to extract
- them from the Turbo C library as needed during the ROM build portion of the
- project. A limited library is built at that time by linking the final MS-DOS
- code, noting the routines used in the code map, and extracting and creating a
- new library of just those routines.
-
-
- Startup Assembly Code
-
-
- Probably the most important piece of code for an embedded system is the system
- startup code (Listing 2). This code will vary from compiler to compiler, but
- the same operations generally appear in some form. The first part of the
- module is usually initialization related to the assembler or compiler. For
- Turbo C, this is the specification of segments. It is here that the order of
- segments for the module is first specified. Turbo C requires three segments,
- MS-DOS requires one.
- MS-DOS requires a stack segment. In a .EXE file, this is the only segment
- specified in the header that is separate from the rest. That is why the stack
- segment is used by some locate utilities as a place to separate RAM code from
- ROM code when generating hex files suitable for EPROM programmers.
- Turbo C requires only three segments, _TEXT, _DATA, and _BSS. These closely
- follow the UNIX C model. The mon86 generated here is a small version,
- therefore, the CODE segment is text only, and the DATA segment is used for
- data, bss (data initialized to zeros), and stack. These segments are mapped
- into DGROUP.
- All 80x86 processors begin executing code after reset at address
- 0xffff:0x0000. The usual procedure is to place a far jump at this location to
- the ROM entry address. For our example, the system entry point is defined as
- the far procedure entry. This label is used as the startup address supplied to
- the locater.
- Next, the processor is initialized. Initializing the processor means
- intializing any segment registers that the processor needs for proper
- operation. Since the module presented here is for an 8086 or 8088 machine,
- only the segment registers must be initialized. Note that segment register
- initialization is performed with all interrupts disabled, which prevents
- spurious interrupts that may occur during power-up from crashing the system.
- Once the processor is initialized, it usually requires the proper setting of
- the stack pointer. For this example, all you must do is initialize the sp and
- bp registers. Note that initializing the bp register is required by Turbo C
- code generation conventions, not by the 8086.
- Special hardware initialization should be performed after the processor has
- been initialized. This includes initializing the interrupt vector table,
- moving the data segment to RAM, and initializing the _BSS segment to all zeros
- to match C programming conventions. The example in Listing 2 includes a hook
- for hardware initialization with a call to __hdw_init. (For clarity, I've
- omitted moving data and initializing vectors.)
- After system initialization, you must enter main so the application can run.
- You enter main by building the stack frame containing the arguments envp,
- argv, and argc. You use these arguments to pass system configuration
- information, dip switch settings, etc. to your C code. These arguments also
- serve as a convenient debugging tool, since you can test different
- configuration switch positions by passing a command line argument during the
- debugging phase.
-
- One critical piece of code that all systems require is the function exit. This
- code is very implementation sensitive. You may want to light LEDs and sound
- alarms. In certain cases, you may want only to display a message and restart.
- What to do when the application terminates must be decided during system
- design. The startup code also contains a call to exit just after the call to
- main, which allows main to return to its caller if an error occurs.
- One final small but necessary bit of code is errno. errno is the location
- where C usually stores an integer value to signify the error condition that
- occurred during a library or system call. Although using errno this way is not
- necessary in all embedded systems, it is safer to follow convention than risk
- an unkown subroutine yielding a link error. Your system call emulation will
- also make use of this variable, allowing for a closer emulation.
-
-
- Operating System Emulation
-
-
- The design example uses a single file for operating system emulation (Listing
- 3). This code closely emulates the standard C equivalents. Following this
- practice minimizes ROM debugging.
- A small data structure, fd, tracks file status. The only parameter set by open
- and close are file open or close status. This structure can be extended to
- satisfy system requirements, but should be kept as simple as possible to
- assure ease of debugging. The open call checks for only a single possible file
- to be opened. /der/con (the control console), stdin, stdout, and stderr are
- also hard-wired as read-only or write-only, as appropriate. In some systems,
- open and close will link to the I/O drivers to perform driver initialization.
- The most used portion of the emulation code is the functions ioctl, read, and
- write. The function ioctl is most often used for setting baud rates, screen
- modes, etc. It is shown here for completeness. The write function simply links
- to the driver call _cout (Listing 4). It maps newlines into carriage return
- and line feed pairs. The read function buffers input. It terminates each
- request on receipt of a newline. Any necessary processing to put text in
- canonical C form should be performed here.
-
-
- Portable Library Functions
-
-
- In order to simplify debugging, standard I/O calls should be emulated. This
- example uses getc, puts, and printf. The functions here must closely emulate
- their respective MS-DOS counterparts, preventing unexpected surprises in the
- target system.
- Examining Turbo C's <stdio.h> file reveals that getc is a macro that invokes
- fgetc (as is typical in many systems). However, _fgetc calls DOS through
- another function, _fgetc. Therefore, your code closely follows this by mapping
- _fgetc and _fputc to return the correct values for success and error (Listing
- 5). A simple puts is implemented in a portable fashion through calls to
- putchar (Listing 6).
- This application uses the abbreviated version of printf described earlier
- (Listing 7). This function only calls write, which minimizes unexpected
- dependencies on other library functions. Guaranteeing that floating point is
- not used helps in not wasting ROM space, usually a premium when system cost is
- considered. In many embedded applications, the only requirements for floating
- point support result from printf's floating point display options.
-
-
- Programming And Debugging
-
-
- Turbo C's greatest asset is its integrated programming environment. A bug can
- be quickly discovered and the code can be quickly recompiled from the same
- environment. Typically, it is in this environment that the first operational
- code is developed. (An interesting note: the new environment for Turbo C++
- includes a register display window that works well with files assembled using
- the masm debugging switch). This phase should be used for all logic testing
- and customer demonstrations, since it is the most flexible of the development
- phases.
- Once the main logic has been debugged, the code should be linked with the
- portable libraries and tested. Listing 8 shows an example make file. Both DOS
- and ROM version targets should be defined, so that any changes that may be
- necessary to the main logic resulting from target system testing can be
- incorporated into the DOS version and retested in the integrated environment.
- The make file presented here also contains two additional targets, ex0 and
- ex0r. These targets are typically limited versions that test the device
- drivers. Quite often, these versions provide repeating patterns and echo
- routines for testing peripherals. Upon satisfactory completion of testing, the
- resulting code is burned into a final EPROM and delivered.
-
-
- Conclusions
-
-
- This example provides a good start for an embedded system design. Although the
- XT and AT architecture is used for this example, the techniques presented here
- are not limited to them. These techniques have been used for new designs in
- projects ranging from simple computer peripherals, such as printers and tape
- transports, to specialized sonar signal analysis hardware. They provide a
- broad approach that can be applied to both native and cross compilations,
- substituting cross compilers and cross compilation targets in the make file.
- Of course, an embedded system need not include support for printf, read, or
- write. An application can instead make simple calls directly to the device
- driver, which results in much smaller code. However, since many tradeoffs are
- considered during the system design phase, more often than not the extra
- functions are worth providing.
-
- Listing 1
- /*************************************
- * mon86.c
- * Simple 8086 rom based monitor.
- **************************************/
-
- #include <stdio.h>
- #include <dos.h>
-
- void debug();
- int smatch();
-
- struct table
- {
- char *t_name; /* the name for this entry */
- int (*t_fn)(); /* the function to execute */
- };
-
- int mem(), go(), error();
-
- struct table cmd[]=
- {
- "mem", mem,
- "go", go,
- "", error
- };
-
-
-
- void main(argc, argv)
- int argc;
- char *argv[];
- {
- if(argc != 2)
- {
- printf("%s: incorrect argument count\n",
- argv [0] );
- exit(1);
- }
- else
- printf("mon86 - demo 8086 monitor\n%s
- version\n", argv[1] );
- debug ();
- }
-
- void debug()
- {
- char line[BUFSIZ];
- char key[32];
- char *lp, *p;
- struct table *tp;
- char *skipwh();
-
- for(;;)
- {
- printf("/nmon86:");
- gets(line);
- lp = skipwh(line);
- if(*lp == NULL)
- continue;
- p = key;
- while(*lp != NULL && !iswhite(*lp))
- *ptt = *lp++;
- *p = NULL;
- for(tp = cmd; *(tp -> t_name) != NULL; tp++)
- if(smatch(key, tp -> t_name))
- break;
- (*(tp -> t_fn))(lp);
- }
- }
-
- int iswhite(c)
- register int c;
- {
- return (c != NULL && (c == ' ' c == '\t,
- c == '\n' c == '\r'));
- }
-
- char *skipwh(p)
- register char *p;
- {
- while(iswhite(*p))
- ++p;
- return p;
- }
-
-
- /**********************************************
- * smatch:
- * match two strings. return true if equal
- *********************************************/
- static int smatch(s1, s2)
- char *s1, *s2;
- {
- while(*s1 != '\0')
- {
- if(*s1 !=*s2)
- break;
- ++s1;
- ++s2;
- }
- return *s1 == *s2;
- }
-
- int mem(lp)
- char *lp;
- {
- unsigned long l = 0l;
- unsigned int seg, offset;
- char far *fp;
- char buf[80];
-
- printf("Memory examine and change\n");
- lp = skipwh(lp);
- while(hexdigit(*lp) >= 0)
- l = (l << 4) + hexdigit(*lp++);
- if(*lp == ':')
- {
- ++lp;
- seg = l;
- l = 0l;
- while(hexdigit(*lp) >= 0)
- l = (l << 4) + hexdigit(*lp++);
- offset = l;
- }
- else
- {
- seg = 0;
- offset = l;
- }
- fp = MK_FP(seg, offset);
- for(;;)
- {
- printf("%04x:%04x - %02x ", FP_SEG(fp),
- FP_OFF(fp), *fp & 0xff);
- gets(buf);
- lp = skipwh(buf);
- if(smatch(".", lp))
- break;
- if(!hexdigit(*lp) >= 0)
- {
- ++fp;
- continue;
- }
- while(hexdigit(*lp) >= 0)
- l = (l << 4) + hexdigit(*lp++);
-
- *fp++ = l & 0xff;
- }
- }
- int go(lp)
-
- char *lp;
- {
- int (far *fp)();
- unsigned long l = 0l;
- unsigned int seg, offset;
-
- lp = skipwh(lp);
- while(hexdigit(*lp) >= 0)
- l = (l << 4) + hexdigit(*lp++);
- if(*lp == ':')
- {
- ++lp;
- seg= l;
- l = 0l;
- while(hexdigit(*lp) >= 0)
- l = (l << 4) + hexdigit(*lp++);
- offset = l;
- }
- else
- {
- seg= 0;
- offset = l;
- }
- fp = NK_FP(seg, offset);
- printf("Go from %04x:%04x", FP_SEG(fp),
- FP_OFF(fp));
- (*fp)();
- }
-
- int error()
- {
- printf("*** Unknown command\n");
- }
-
- hexdigit(c)
- int c;
- {
- register char *p;
- register i;
- static char *set = "0123456789abcdef",
- *alt_set = "0123456789ABCDEF";
-
- for(i = 0, p = set; *p != NULL; i++, p++)
- if(c == *p)
- return i;
- for(i = 0, p = alt_set; *p != NULL; i++, p++)
- if(c == *p)
- return i;
- return -1;
- }
-
-
- Listing 2
- page 60,132
-
- title start1 - simple start-up code
- ;**************************************************************;
- ; ;
- ; start1.asm ;
- ; ;
- ; start-up for stand-alone C code - Turbo C/C++ version ;
- ; ;
- ; created: November 17, 1990 ;
- ; ;
- ; Copyright (c) 1990 ;
- ; Pasquale J. Villani ;
- ; All Rights Reserved ;
- ; ;
- ;**************************************************************;
-
- _TEXT segment byte public 'CODE'
- assume cs:_TEXT ; small model
- _TEXT ends
-
- _DATA segment word public 'DATA'
- DGROUP group _DATA,_BSS,_BSSEND ; small model
- assume ds:DGROUP,ss:DGROUP
- _DATA ends
-
- _BSS segment word public 'BSS'
- _BSS ends
-
- _BSSEND segment byte public 'STACK'
- _BSSEND ends
-
- _TEXT segment byte public 'CODE'
-
- extrn _main:near ; C entry point
- extrn _hdw_init:near ; Hardware init (asm or C)
-
- ;
- ; entry:
- ; Our ROM madule's entry point. This should be used as the
- ; 'jmp far ptr entry' at the top of ROM.
- ;
- entry proc far
- public entry;
-
- ;
- ; First order of business is the initialization of the
- ; machine itself. This is basically the initialization
- ; of any segment registers that the processor needs for
- ; proper operation. On an 80X86 class processor, this is
- ; usually initializing the segmentation registers, going
- ; to the proper mode (i. e. protected, etc.) for
- ; 80[234]86 processors, etc. This file is for an 8086 or
- ; 8088 machine (or any other in real mode). Segment
- ; registers are the only processor initialization.
- ;
- ; NOTE: the cs register is not initialized because the
- ; processor itself initializes the first cs, followed
- ; by the far jump changing it to point to segment entry.
- ;
- cli ; prevent interrupts while starting
-
- mov ax,DGROUP ; initialize the segment registers
- mov ds,ax
- mov ss,ax
- mov es,ax
- ;
- ; Once the processor is initialized, it usually requires
- ; the proper setting of the stack pointer(s). For the
- ; 8086 case, all we need is to initialize the sp and bp
- ; registers.
- ;
- mov sp,offset DGROUP:tos
- mov bp,sp
- ;
- ; We are now ready to make any special hardware
- ; initialization. This includes initializing the interrupt
- ; vector table, moving the data segment to RAM and
- ; initializing the BSS to zero to match C programming
- ; conventions. This should end with a call to __hdw_init
- ; to initialize I/O, etc.
- call near ptr __hdw_init; initialize the system without ints
- sti ;now enable them
- ;
- ; After the hardware is initialized, we wish to enter
- ; main(). This is accomplished by building the stack for
- ; envp, argv and argc. This is a convienent mechanism
- ; for passing system configuration, dip switch settings,
- ; etc. to our C code.
- ;
- mov ax,offset env ; envp for this example
- push ax
- mov ax,offset args ; argv for this example
- push ax
- mov ax,2 ; argc = 2
- push ax
- call near ptr _main ; finally enter C code
- add sp,6 ; clean stack
- ;
- ; If main should return, we need a mechanism to catch this
- ; condition. Depending on the system design, we should
- ; alarm, restart, etc., therefore, the following code
- ; is very implementation dependant. In our case, all we
- ; want to do is call exit with a special -1 exit code.
- ;
- mov ax,-1
- push ax
- call near ptr _exit
- jmp $ ; belt and suspenders
-
- entry endp
-
- ;
- ; exit:
- ; Where to go for error conditions (typically) or shutdown
- ; conditions. This code is very implementaion sensitive, since
- ; we may want to light LEDs and sound alarms. In this case, we'll
- ; only silently stop. In certain cases, we may want to only
- ; display a message and restart. This should be decided upon
- ; during system design.
- ;
-
- _exit proc near
- public _exit
-
- cli
- hlt
- jmp _exit
-
- _exit endp
-
- T_EXT ends
-
- _DATA segment word public 'DATA'
- env0 label byte
- db 'ENV=ROM',0
- arg0 label byte
- db 'ex0s', 0
- arg1 label byte
- db 'rom', 0
- env label word
- dw DGROUP:env0
- dw 0
- args label word
- dw DGROUP:arg0
- dw DGROUP:arg1
- dw 0
- _DATA ends
-
- ;
- ; The stack segment. This size should be adjusted to fit
- ; any particular system requirements.
- ;
- _STACK SEGMENT
- dw 512 dup (?)
- tos label byte
- dd (?) ; safety area
- last label word ; must always be end of stack area
- _STACK ENDS
-
- _BSS segment word public 'BSS'
- _errno label word ; c lib errno
- public _errno
- dw (?)
- _BSS ends
-
- _BSSEND segment byte public 'STACK'
- _BSSEND ends
-
- end
-
-
- Listing 3
- /***************************************/
- /* */
- /* osem.c */
- /* */
- /* Operating system emulation routines */
- /* for embedded system example ROM */
- /* monitor. */
- /* */
-
- /* Copyright (c) 1990 */
- /* Pasquale J. Villani */
- /* All Rights Reserved */
- /* */
- /* */
- /***************************************/
-
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <errno.h>
-
- /* convience defines */
- #define STDIN 0
- #define STDOUT 1
- #define STDERR 2
- #define MAXFD 3
- #define BUFSIZ 512 /* buffers are 512 for this vsn */
-
- #ifndef TRUE
- #define TRUE 1
- #endif
- #ifndef FALSE
- #define FALSE 0
- #endif
- #ifndef ERROR
- #define ERROR -1
- #endif
- #ifndef OK
- #define OK 0
- #endif
- #ifndef EOF
- #define EOF -1
- #endif
-
- /* make errno accessible to the entire file */
- extern int errno;
-
- /* special data structure for ioctl optional argument */
- union arg
- {
- int i;
- void *p;
- };
-
- /* a simple file table to keep track of stdin & stdout status */
- static char fd[MAXFD] =
- {
- FALSE, /* stdin */
- FALSE, /* stdout */
- FALSE /* stderr */
- };
-
- /* a simple set of i/o buffers for input buffering */
- struct _buf
- {
- int nbytes;
- char *bp;
- char buf[BUFSIZ];
-
- };
-
- static struct _buf iobuf =
- {
- -1, (char *)0
- };
-
- /* */
- /* smatch: */
- /* match two strings. return true if equal */
- /* */
- static int smatch(s1, s2)
- char *s1, *s2;
-
- {
- while(*s1 != '\0')
- {
- if(*s1 != *s2)
- break;
- ++s1;
- ++s2;
- }
- return s1 == s2;
- }
-
- /* */
- /* open: */
- /* open a file for read or write */
- /* follows posix specification */
- /* */
- int open(path, oflag, mode)
- char *path;
- int oflag, mode;
- {
- if(smatch(path, "/dev/con") ((oflag & O_RDWR) == O_RDWR))
- {
- errno = EACCES;
- return ERROR; /* error - only console allowed */
- }
- /* for this system, only stdin and stdout are available
- /* based on oflag and availability, assign appropraie fd
- if((oflag & O_RDONLY) && !fd[STDIN])
- {
- fd[STDIN] = TRUE;
- return STDIN;
- }
- else if(oflag & O_WRONLY)
- {
- fd[STDOUT] = TRUE;
- return STDOUT;
- }
- else
- {
- errno = EACCES;
- return ERROR;
- }
- }
-
- /* */
-
- /* close: */
- /* close a file */
- /* */
- int close(fildes)
- int fildes;
- {
- if((fildes < 0) (fildes > MAXFD) !(fd[fildes]))
- {
- errno = EBADF;
- return ERROR;
- }
- fd[fildes] = FALSE;
- return OK;
- }
-
- /* */
- /* ioctl: */
- /* direct device control */
- /* */
- int ioctl(fildes, request, arg)
- int fildes, request;
- union arg arg;
- {
- errno = EINVAL;
- return ERROR;
- }
- /* */
- /* write: */
- /* write to a file */
- /* */
- int write(fildes, buf, nbyte)
- int fildes;
- void *buf;
- unsigned int nbyte;
- {
- int cnt = nbyte;
- if(fildes != STDOUT && fildes != STDERR)
- {
- errno = EBADF;
- return ERROR;
- }
- while(cnt-- > 0)
- {
- if(*(char *)buf == '\n')
- _cout('\r');
- _cout(*((char *)buf)++);
- }
- return nbyte;
- }
- int read(fildes, buf, nbyte)
- int fildes;
- void *buf;
- unsigned int nbyte;
- {
- register char *bp;
- register int c;
-
- if(fildes != STDIN)
- {
-
- errno = EBADF;
- return ERROR;
- }
- if(iobuf.nbytes <= 0)
- {
- bp = iobuf.buf;
- iobuf.nbytes = 0;
- do
- {
- *bp++ = (_cout(c = _cin()));
- if(c == '\r')
- _cout('\n');
- ++iobuf.nbytes;
- } while(c != '\n' && c != '\r');
- iobuf.bp = iobuf.buf;
- }
- for(bp = iobuf.bp, c = 0; c < iobuf.nbytes && c <nbyte; c++)
- {
- *((char *)buf)++ = *bp == '\r' ? ++bp, '\n' : *bp++;
- }
- iobuf.bp = bp;
- iobuf.nbytes -= c;
- return c;
- }
-
-
- Listing 4
- ;
- ; _cio - console i/o for embedded system example
- ; near version
- ;
-
- _TEXT segment byte public 'CODE'
- DGROUP group _DATA,_BSS
- assume cs:_TEXT,ds:DGROUP,ss:DGROUP
- _TEXT ends
-
- _DATA segment word public 'DATA'
- _DATA ends
-
- _BSS segment word public 'BSS'
- _BSS ends
-
- _TEXT segment byte public 'CODE'
-
-
- ;
- ; _cin - input to console
- ; near version
- ;
-
- __cin proc near
- public __cin
- push bp ; perform c entry
- mov bp,sp
- push bx ; save bx for c
- mov ah,0h ; get the byte
- mov bx,7
- int 16h ; using the bios
-
- mov ah,0 ; "sign extend" - convert to int
- pop bx ; restore context
- pop bp
- ret
- __cin endp
-
- ;
- ;_cout - output to console
- ; near version
- ;
-
- __cout proc near
- public __cout
- push bp ; c entry
- mov bp,sp
- push bx ; save register for c
- mov ax,word ptr [bp+4] ; get c
- mov ah,0eh ; and output the byte
- mov bx,7
- int 10h ; call bios
- pop bx ; restore context
- pop bp
- ret
- __cout endp
-
- ;
- ; _cinit - console initialization
- ; **** No initialization required for bios version ****
- ;
- ; near version
- ;
- __cinit proc near
- public __cinit
- push bp
- ; perform any required hardware initialization here
- mov ax,1 ; return true for success
- pop bp
- ret
-
- __cinit endp
-
- _TEXT ends
-
- end
-
-
- Listing 5
- /****************************************************/
- /* */
- /* iolink.c */
- /* */
- /* stdio tie for Turbo C/C++ Compilers */
- /* */
- /* Copyright (C) 1990 */
- /* Pasquale J. Villani */
- /* All Rights Reserved */
- /* */
- /****************************************************/
-
-
- #include <stdio.h>
-
- FILE _streams[20];
-
- fputc(c, f)
- char c;
- FILE *f;
- {
- return _fputc(c, f);
- }
- _fputc(c, f)
- char c;
- FILE *f;
- {
- if(write(1, &c, 1) == 1)
- return c;
- else
- return EOF;
- }
-
- _fgetc(f)
- FILE *f;
- {
- char c;
-
- if(read(0, &c, 1) != 1)
- return EOF;
- else
- return c;
- }
-
-
- Listing 6
- /*************************************************/
- /* */
- /* puts.c */
- /* */
- /* Put a string to console. Uses stdio.h putchar */
- /* macro for operating system interface. Outputs */
- /* a newline after the string */
- /* */
- /* Copyright (c) 1990 */
- /* Pasquale J. Villani */
- /* All Rights Reserved */
- /* */
- /* */
- **************************************************/
-
- #include <stdio.h>
-
- puts(s)
- const char *s;
- {
- int c;
-
- while ((c = *s++) != '\0')
- putchar(c);
- putchar('\n');
- }
-
-
-
- Listing 7
- /*
- * abbreviated printf routine
- *
- * embedded system version
- * 11/30/90
- */
-
- /* ltob -- convert an long integer to a string in any
- base (2-36) */
- static char *ltob(n, s, base)
- long n;
- char *s;
- {
- unsigned long u;
- register char *p, *q;
- register negative, c;
-
- if (n < 0 && base == -10)
- {
- negative = 1;
- u = -n;
- }
- else
- {
- negative = 0;
- u = n;
- }
- if (base == -10) /* signals signed
- conversion */
- base = 10;
- p = q = s;
- do
- { /* generate digits in reverse
- order */
- *p++ = "0123456789abcdef"[u % base];
- } while ((u /= base) > 0);
- if (negative)
- *p++ = -;
- *p = '\0'; /* terminate the string */
- while (q < --p)
- { /* reverse the digits */
- c = *q;
- *q++ = *p;
- *p = c;
- }
- return s;
- }
-
- #define NONE 0
- #define LEFT 1
- #define RIGHT 2
-
- /* printf -- short version of printf to conserve
- space */
- int printf(fmt, args)
- const char *fmt;
-
- char *args;
- {
- register base;
- register char **arg;
- char s[11], *p, *ltob();
- char c, slen, flag, size, fill;
-
- arg = &args;
- flag = NONE;
- size = 0;
- while ((c = *fmt++) != '\0')
- {
- if (size == 0 && flag == NONE && c != '%')
- {
-
- write(1, &c, 1);
- continue;
- }
- switch(*fmt)
- {
- case '-':
- flag = RIGHT;
- fill = *(fmt + 1) == '0' ? '0' : ' ';
- continue;
-
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- if(flag == NONE)
- flag = LEFT;
- size = *fmt++ - '0';
- while((c = *fmt++) != '\0')
- {
- switch(c)
- {
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- size = size * 10 + (c - '0');
- continue;
-
- default:
- --fmt;
- break;
- }
-
- break;
- }
- break;
- }
- switch (c = *fmt++)
- {
- case 'c':
- write(1, (char *)arg++, 1);
- continue;
-
- case 'd':
- base = -10;
- goto prt;
-
- case 'o':
- base = 8;
- goto prt;
-
- case 'u':
- base = 10;
- goto prt;
-
- case 'x':
- base = 16;
-
- prt:
- ltob((long)(*((int *)arg)++), s, base);
- if(flag == RIGHT flag == LEFT)
- {
- for(slen = 0, p = s; *p != '\0'; p++)
- ++slen;
- }
- if(flag == RIGHT && slen < size)
-
- {
- int i;
-
- for(i = size - slen; i > 0; i--)
- write(1, &fill, 1);
- }
- for(p = s; *p != '\0'; p++)
- write(1, p, 1);
- if(flag == LEFT)
- {
- int i;
- char sp = ' ';
-
- for(i = size - slen; i > 0; i--)
- write(1, &sp, 1);
- }
- size = 0;
- flag = NONE;
- continue;
-
- case 'l':
- switch(c = *fmt++)
- {
- case 'd':
- base = -10;
-
- goto lprt;
-
- case 'o':
- base = 8;
- goto lprt;
-
- case 'u':
- base = 10;
- goto lprt;
-
- case 'x':
- base = 16;
-
- lprt:
- ltob(*((long *)arg)++, s, base);
- if(flag == RIGHT flag == LEFT)
- {
- for(slen = 0, p = s; *p != '\0'; p++)
- ++slen;
- }
- if(flag == RIGHT && slen < size)
- {
- int i;
-
- for(i = size - slen; i > 0; i--)
- write(1, &fill, 1);
- }
- for(p = s; *p != '\0'; p++)
- write(1, p, 1);
- if(flag == LEFT)
-
- {
- int i;
- char sp = ' ';
-
- for(i = size - slen; i > 0; i--)
- write(1, &sp, 1);
- }
- size = 0;
- flag = NONE;
- continue;
-
- default:
- write(1, &c, 1);
- }
-
- case 's':
- for(p = *arg; *p != '\0'; p++)
- write(1, p, 1);
- ++arg;
- continue;
-
- default:
- write(1, &c, 1);
- continue;
- }
- }
- }
-
-
-
- Listing 8
- #
- # makefile for example 0
- #
- # 12/02/90 1830 EST
- #
-
- CC = tcc
- CFLAGS = -ms -v
- AS = masm
- ASFLAGS = /Mx/Zi
- LD = tlink
- LDFLAGS = /v/c
-
- OBJSROM = start1.obj cio.obj osem.obj iolink.obj\
- hdwinit.obj prf.obj puts.obj gets.obj
- LIBSROM = libm.lib
-
- all: ex0.exe ex0s.exe mon86.exe mon86r.exe
-
- mon86.exe: mon86.obj
- $(CC) $(CFLAGS) -emon86 mon86
-
- osem.obj: osem.c
- $(CC) $(CFLAGS) -c osem.c
-
- mon86.obj: mon86.c
- $(CC) $(CFLAGS) -c mon86.c
-
- iolink.obj: iolink.c
- $(CC) $(CFLAGS) -c iolink.c
-
- prf.obj: prf.c
- $(CC) $(CFLAGS) -c prf.c
-
- puts.obj: puts.c
- $(CC) $(CFLAGS) -c puts.c
-
- gets.obj: gets.c
- $(CC) $(CFLAGS) -c gets.c
-
- start0.obj: start0.asm
- $(AS) $(ASFLAGS) start0,,,;
-
- ex0s.exe: ex0.obj $(OBJSROM)
- $(LD) $(LDFLAGS) $(OBJSROM) ex0.obj,\
- ex0s,nul,$(LIBSROM)
-
- mon86r.exe: mon86.obj $(OBJSROM)
- $(LD) $(LDFLAGS) $(OBJSROM) mon86.obj,\
- mon86r,nul,$(LIBSROM)
-
- start1.obj: start1.asm
- $(AS) $(ASFLAGS) start1,,,;
-
- cout.obj: cout.asm
- $(AS) $(ASFLAGS) cout,,,;
-
-
- cin.obj: cin.asm
- $(AS) $(ASFLAGS) cin,,,;
-
- cio.obj: cio.asm
- $(AS) $(ASFLAGS) cio,,,;
-
- ex0.obj: ex0.c
- $(CC) $(CFLAGS) -c ex0.c
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Pointer-Pointers In C
-
-
- Anthony Dos Reis and Li Yun
-
-
- Anthony Dos Reis is an assistant professor of computer science at the College
- of New Paltz in New York. Li Yun is a graduate student in the college's
- Mathematics and Computer Science Department.
-
-
- Pointer-pointers are literally pointers that point to other pointers. They are
- used in functions that must change the value stored in a parameter. Because
- variables in C are passed by value, not by reference, such functions must be
- passed the variable's address (pass-by-reference), not its value. A parameter
- can be a pointer. A pointer's address is, of course, just a pointer-pointer.
- For example, the increment function below increments an integer pointer. It
- declares parameter p as
- int **p;
- Breaking this declaration into two parts -- int * and *p -- makes its meaning
- clear. *p indicates that p is a pointer. int * means that p points to an
- integer pointer. In the function body, (*p) references the integer pointer to
- which p points. Thus, (*p)++ increments this integer pointer.
- increment (p)
- int **p;
- {
- (*p)++;
- }
- Using a typedef statement for int* simplifies the parameter declaration.
- typedef int * LINK;
- increment (p)
- LINK *p;
- {
- (*p)++;
- }
- Using pointer-pointers carefully can considerably reduce the complexity of
- functions that perform pointer manipulation. For example, consider two
- functions that both add a node to a binary search tree. The first function,
- add1, uses the standard textbook technique of two pointers, one trailing the
- other, to determine the insertion point for the new node. The two pointers,
- current and previous, move down the tree until current takes the value NULL.
- The function then adds the new node to the previous node. Notice that though
- add1 is passed a pointer-pointer (rootpp), its use is limited to accessing the
- root pointer of the tree.
- The second function, add2, uses a single pointer-pointer, pp, that moves down
- the tree pointing to either the node's left or right pointer fields. When the
- pointer reaches the appropriate leaf, the function adds the new node.
- The add1 function is more complex than add2 in several respects. First, it
- moves two pointers, current and previous, instead of one. Second, add1
- performs a special test to determine if the tree is NULL (in which case the
- new node becomes the root of the tree). add2 needs no special test. Third,
- after finding the leaf at which the insertion must occur, add1 must determine
- whether to add to the left or to the right of the leaf, even though this
- left/right test was just performed in the preceding while loop. In contrast,
- add2 exits its while loop with pp pointing to the left or right field that
- must be modified to add the new node.
- Pointer-pointers may be a bit harder to understand at first, but the
- investment can pay off in simpler code.
-
- Listing 1
- /*-------------------------------------
- * This program was used to test the
- * add1 and add2 functions
- *-------------------------------------*/
- #include <stdio.h>
-
- typedef struct NODE* LINK;
-
- struct NODE {
- int info;
- LINK left;
- LINK right;
- };
-
- main()
- {
- LINK rootptr1=NULL,rootptr2=NULL;
- int x;
-
- printf("Enter integers terminated \
- with ctrl z\n");
- /* Create tree */
- while (scanf("%d",&x) == 1) {
-
- add1(&rootptr1,x);
- add2(&rootptr2,x);
-
- }
- printf("Using add1\n");
- recurse_traverse(rootptr1); /* Traverse tree */
- printf("Using add2\n");
- recurse_traverse(rootptr2);
- }
- recurse_traverse(rootptr) LINK rootptr;
- {
- if (rootptr != NULL) { /* Is tree null? */
- recurse_traverse(rootptr -> left); /* Recurse */
- printf("%d\n",rootptr -> info);
- recurse_traverse(rootptr -> right); /* Recurse */
- }
- }
- add1(rootpp,val) LINK *rootpp;int val;
- {
- LINK newnode,current,previous;
-
- /* Create a new node */
- newnode = (LINK) malloc(sizeof(struct NODE));
- newnode->info = val;
- newnode->left = NULL;
- newnode->right = NULL;
- current = *rootpp;
- previous = NULL;
- while (current != NULL) { /* Search for leaf */
- previous = current;
- if (val < current->info)
- current = current->left;
- else
- current = current->right;
- }
- if (previous == NULL) /* Null tree? */
- *rootpp = newnode; /* Node becomes tree */
- else
- if (val < previous->info) /* Add left/right? */
- previous->left = newnode; /* Add to left */
- else
- previous->right = newnode; /* Add to right */
-
- }
- add2(pp,val) LINK *pp; int val;
- {
- LINK newnode;
-
- /* Create a new node */
- newnode = (LINK) malloc(sizeof(struct NODE));
- newnode->info = val;
- newnode->left = NULL;
- newnode->right = NULL;
- while (*pp != NULL) { /* Search for leaf */
- if (val < (*pp)->info)
- pp = &(*pp)->left; /* Move pp */
- else
- pp = &(*pp)->right; /* Move pp */
- }
- *pp = newnode; /* Add node to tree */
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- X Window Programming
-
-
- Part 1: The X Window System
-
-
-
-
- Eric F. Johnson and Kevin Reichard
-
-
- Johnson and Reichard are authors of X Window Applications Programming,
- Advanced X Window Applications Programming and Power Programming Motif (all
- MIS: Press, 800-MANUALS). Johnson can be reached at erc@pai.mn.org via UUCP
- mail. Reichard's Compuserve address is 73670,3422.
-
-
- As any C programmer who has gone near Windows, OS/2, or the Macintosh knows,
- programming in graphical interface environments can be a struggle.
- Furthermore, the resulting application is usually tied to the machine for
- which it was written, defying the portability C provided in the first place.
- There exists, however, a graphical interface programming environment that
- retains the portability associated with C and that runs on everything from an
- Amiga to an IBM PS/2 to a Cray supercomputer -- the X Window System.
- X acts as an elegant and highly functional link between disparate systems,
- serving as a common interface across many different computers running
- different operating systems with several different displays. For instance,
- using X, Cray supercomputers can display output on an Apollo workstation, on
- an Amiga, or on any X station across a network. Major computer vendors, such
- as Apple, Xerox, IBM, Hewlett-Packard, and Sun, offer and promote X products.
- For the programmer, the X Window System offers a variety of tools, shared
- resources and widespread portability. For the user, X Window provides a
- consistent interface, regardless of the platform.This article begins a series
- that will cover some basic X Window programming techniques. In this series, we
- plan to cover not only the mechanics of X Window programming, but also the
- theoretical underpinnings that make X such a unique programming environment.
-
-
- Why X?
-
-
- Several reasons explain why X has gained momentum in the workplace. First of
- all, X is an open environment. MIT holds rights to X, but allows unrestricted
- use. Thus the X developer isn't tied to a single vendor (such as Microsoft/IBM
- and MS-DOS).
- The X Window System is also a graphical windowing system. Studies indicate
- that graphical interfaces make computing easier and users more productive.
- X has also piggybacked on the growing interest in UNIX. While not designed
- specifically for UNIX, X's greatest acceptance as a windowing system has been
- on UNIX. Unlike other UNIX windowing systems, such as NeWS from Sun and the
- NeXT interface, X is an open system usable without licensing fees.
- In an era where linking different kinds of computers is paramount for software
- designers, broad-based acceptance for a windowing system is paramount for
- cross-vendor development. X's client-server model makes it possible to link
- disparate makes of hardware without costly emulation cards and exotic
- networking schemes. The user interface, which makes only X calls, doesn't rely
- on any one operating system.
- A list of important X features would include the following characteristics.
-
-
- Portability
-
-
- X runs on computers ranging from the Amiga personal computer to the Cray-2
- supercomputer. You will find X on PCs, Macintoshes, Hewlett-Packards, PS/2s,
- Suns, Data Generals, DECs, and most everything else. Barring a few minor
- differences, the C interface is the same across all these platforms, a major
- benefit over most other graphics systems.
-
-
- Network Transparency
-
-
- An X program, if written correctly, can run on one machine and display its
- output on another machine. X, by being operating-system independent,
- encourages portable software. X's standard C library routines are common
- across hardware platforms, meaning that your interface code ports directly
- from one machine to the other.
-
-
- Vendor Backing
-
-
- X runs on most every UNIX workstation available today. Digital Equipment,
- Hewlett-Packard, IBM, and Apple all offer their own versions of X. Other
- vendors offer the generic MIT X with their workstations, such as Data
- General's Aviion series. For MS-DOS users, several companies -- Quarter-deck
- and Integrated Inference among them -- offer versions of X for MS-DOS and
- Windows. Those who use UNIX on a PC will discover that almost all major
- vendors -- Interactive, SCO, Apple, Everex -- offer X Window with their UNIX
- systems. And finally, other vendors have merged X with proprietary windowing
- systems (NeWS in the case of Sun Microsystems) and now offer merged X servers.
-
-
- Inexpensive Licensing
-
-
- You can obtain the X sources (written in C) free. The information later in
- this article will help you order X, sually for the price of a tape and a small
- handling fee. You are not required to pay licensing fees if you release
- X-based applications.
-
-
- Shared Resources
-
-
- The X Window System allows programs to concurrently share devices such as
- mice, keyboards, and graphics displays. With X, your entire workstation is a
- display, consisting of a keyboard, a pointing device (usually a mouse), and
- one or more monitor screens. Multiple screens can work together, linked by the
- keyboard and the pointing device.
-
- Lest we give the impression that X is a bed of roses, we should make it clear
- that X is a high-level programming environment. A good deal of X development
- today is done at the workstation level. PC users can't merely slap X on a hard
- disk and expect a high-performance system. Those interested in X should obtain
- as much RAM as possible and invest in large hard drives -- 80Mb is good, 120Mb
- is better, and 300Mb is even better. You should look into high- resolution
- displays, and in networking cards, Ethernet, and TCP/IP. As for software
- development expertise, there's a certain amount of sophistication required to
- program in X Window. Experience with C programming and net- working is
- necessary.
-
-
- The History Of X
-
-
- In 1984, Massachusetts Institute of Technology (MIT) officials were faced with
- a motley set of incompatible workstations acquired through donation and
- purchase. Their goal was to build a network of graphical workstations. Faced
- with a crazy quilt of operating systems and hardware vendors, MIT officials
- formed Project Athena, an MIT development team working in association with DEC
- and IBM.
- Project Athena's solution was to design a network-based windowing system that
- would run local applications while being able to call on remote sources. This
- system was loosely based on a Stanford University software environment called
- W. By linking these workstations through a graphical networking environment,
- the designers created the first operating environment that was truly hardware
- and vendor independent: The X Window System.
- The development team had the following goals:
- Do not add new functionality unless an implementation cannot complete a real
- application without it.
- Do not serve all the world's needs, but make the system extensible so that
- additional needs can be met in an upwardly compatible fashion.
- If a problem is not completely understood, it is probably best to provide no
- solution at all.
- If you get 90 percent of the desired effect for 10 percent of the work
- required to get 100 percent, use the simpler solution.
- Isolate complexity as much as possible.
- Provide mechanism rather than policy. In particular, place user-interface
- policy in the client's hands.
- By 1986, the outside world was clamoring for access to the X Window system. In
- March 1988, MIT officially issued Version 11, Release 2. Later that year,
- Release 3 became available. In January 1990, Release 4 was unleashed. As of
- this writing, only a few vendor-supported X products are at Release 4. Release
- 5 will probably appear in late 1991.
- When the X Consortium people refer to X, they call it the "X Window System,"
- "X Window," "X11," or just plain X." People in the know never call it "X
- Windows" (note the "s").
-
-
- What Is X?
-
-
- The X Window System is based on a client/server relationship. The display
- server program controls and draws all output to the display monitors--that is,
- it draws the dots on your physical monitor, tracks client input, and updates
- windows accordingly. Clients are application programs that perform specific
- tasks. (The terms client and applications are used interchangeably.) Because X
- is a networked environment, the client and server don't necessarily compute on
- the same system (although they certainly can in a number of situations).
- Instead, X allows distributed processing. For example, a Macintosh running
- A/UX as an X server can call upon a Cray supercomputer's processing power
- within the network, displaying the Cray's computations on the Mac's monitor.
- In the micro and mini worlds, a server is the hardware device at the center of
- a network, distributing data and processing power to networked workstations
- and terminals. In contracts, an X server is a local software program that
- controls the display hardware and runs on your local workstation. Because
- other systems on the network can access your display, you cannot view the X
- server as a file server in local area networks (LANs).
- In X, a display consists of a keyboard, pointing device (usually a mouse), and
- one or more screens. The display server tracks multiple input, allowing users
- to run several clients (such as a database manager, word processor, and
- graphics application). A display can power multiple screens linked by a
- keyboard and mouse.
- The server controls traffic between clients, or applications, running on local
- or remote systems. It also controls the power of the local system. The server:
- allows access to a display by a client
- sends network messages
- intercepts other network messages from other clients
- performs two-dimensional drawing, freeing the client from processing-intensive
- graphics
- tracks resources (such as windows, cursors, fonts, and graphics contexts) that
- are shared between clients
- allows distributed processing
- allows multitasking, if X is used with a multitasking operating system (such
- as UNIX)
- Most importantly, the server tracks input from the display and informs the
- clients. In X, such inputs are called events. Pressing down on a key is an
- event; letting the key back up is another event. Similarly, moving a cursor
- with a mouse is an event. These events are delivered to applications through
- an event queue.
-
-
- The Sum Of Its Parts
-
-
- MIT's generic X Window System consists of the Xlib graphics subroutine
- library, the X network protocol, an X toolkit, and several window managers
- (such as twm). The application programmer links the client program through
- Xlib.
- Xlib is the main programmer's interface to X. Xlib is a low-level library
- providing access to X graphics and interface functions. Unlike most DOS-based
- graphics, however, these low-level routines don't directly munge the hardware.
- With the Xlib routines, you almost never directly interface to the underlying
- hardware. While this may not seem optimal for performance, it does provide X's
- much-vaunted portability.
- Xlib provides functions and macros for drawing lines, creating windows,
- displaying text and defining colors. Xlib hides most of the gory details of
- connecting with an X server and maintaining network links.
- Xlib contains about 300 routines that map to X Protocol requests or provide
- utility functions. Xlib converts C function calls to the X protocol request
- that actually implements the given function, such as XDrawLine to draw a line.
- These functions include creating, destroying, moving, and sizing windows;
- drawing lines and polygons; setting background patterns; and tracking the
- mouse. Xlib provides various ways to access windows, including overlapping and
- simultaneous output to multiple windows. It supports multiple fonts, raster
- opera- operations, line drawing, and both color and monochrome applications.
- All of these features facilitate portability, so your program should work the
- same on a Sun workstation as it does on a IBM PS/2 running SCO Open Desktop.
- Above Xlib sit many X toolkits, such as Motif and Open Look.
- X toolkits are collections of sub-routines that can make programming easier.
- They are prewritten graphics routines -- you can put together different parts
- to form a program. Different vendors are constantly revising toolkits.
- These toolkits (after a steep initial learning curve) can speed the creation
- of X applications. Even if you plan on using toolkits exclusively, you will
- still need to understand how Xlib operates in order to create quality,
- commercial- grade X applications.
- The X network protocol defines data structures used to transmit requests
- between clients and servers. The X network protocol is not based on procedure
- calls or a kernel-call interface. It is an asynchronous stream-based
- inter-process communication.
- The X Consortium supplies the protocol specification on tape. X Window System
- Protocol (Version 11) by Robert Scheifler defines the protocol specification.
- X Window System Protocol is included with the distribution tape. It can be
- found on UNIX systems under ~TOP-LEVEL/mit/doc/Protocol/spec (TOP-LEVEL is the
- base of your X directory tree).
-
-
- Obtaining X Window
-
-
- If you're a workstation or UNIX user, you can easily obtain the X Window
- System free, and you don't need to license X or pay royalties.
- You can obtain the X Window system directly from MIT, through Internet and
- UUCP, and from commercial consulting firms and vendors.
- MIT sells X on a set of four 1,600bpi tapes in UNIX tar format. A book (X
- Window System: The Complete Reference to Xlib, X Protocol, ICCCM, XLFD) and
- manuals sell for $125. Tapes, book, and manuals sell for $400. If interested,
- write to:
- MIT Software Distribution Center
- Technology Licensing Office, room
- E32-300
-
- 77 Massachusetts Ave.
- Cambridge, MA 02139
- 617/258-8330 (the "X Ordering
- Hotline")
- If you're on Internet or UUCP, you can retrieve the release from the locations
- shown in Table 1.
- The FTP directories contain a README file with further instructions. (We don't
- have room to print the README file, but you should persue that file before
- proceeding.)
- For DOS or PC users, implementing X means purchasing it from a commercial
- vendor. Many UNIX vendors support X Window. SCO, for instance, offers X Window
- and Motif as part of its Open Desktop software. Interactive Systems provides X
- Window and Motif as part of 386/ix. Apple Computer integrates X Window and the
- Macintosh operating system in v2.0 of A/UX. Hummingbird Communications
- (416/470-1203) sells HCL-eXceed, a family of X servers for DOS-based PCs.
- XVision (Visionware; sold through UniMarket 800-222-0550) displays both DOS
- and X Window applications on a PC. PC-XView (again through UniMarket) turns a
- PC into an X Window terminal.
- Details regarding Desqview/X from Quarterdeck have been floating around for
- months. We haven't seen the product, but it appears to be an interesting
- marriage of X and DOS. Integrated Inference Machines (714-978- 6776) offers
- X11/AT, a marriage of MS-Windows and the X Window System.
- You can also obtain X Window from consulting firms:
- Integrated Computer Solutions
- 163 Harvard St.
- Cambridge, MA 02139
- 617-547-0510
- info@ics. com; uunet!ics.com!info
- O'Reilly and Associates
- 632 Petaluma Ave.
- Sebastopol, CA 95472
- 800/338-6887
- In addition, the following workstation and software vendors include the X
- Window System in their products: AT&T, Apple, Bull, DEC, Data General, Everex,
- Hewlett-Packard, IBM, IXI Limited, Intel, Interactive Systems, Motorola, NCR,
- Open Software Foundation, SCO, Solbourne, Sony, Sun Microsystems, Visix, and
- many more every day.
-
-
- Information On Programming With X
-
-
- These books are geared toward programming in X Window, and specifically Xlib.
- During this series of articles, we'll mention other books tied to specific
- topics. X beginners may want to start with any of the books on the following
- list. The easiest way to learn X programming is by doing it, so have fun.
- Johnson, Eric F. and Kevin Reichard, X Window Applications Programming, MIS:
- Press, Portland, OR, 1989. ISBN 1-55828-016-2.
- Johnson, Eric F. and Kevin Reichard, Advanced X Window Applications
- Programming, MIS: Press, Portland, OR, 1990. ISBN 1-55828-029- 4.
- Jones, Oliver, Introduction to the X Window System, Prentice-Hall, Englewood
- Cliffs, NJ, 1989. ISBN 0-13- 499997-5.
- Nye, Adrian, Xlib Programming Manual, Vol. 1, 2nd Ed., O'Reilly and Assoc.,
- Sebastopol, CA, 1990. ISBN 0- 937175-11-0.
- Nye, Adrian (ed.), Xlib Reference Manual, vol. 2, 2nd Ed., O'Reilly and
- Assoc., Sebastopol, CA, 1990. ISBN 0- 937175-12-9.
- Scheifler, Robert W. and James Gettys, with Jim Flowers, Ron Newman, and David
- Rosenthal, X Window System: The Complete Reference to Xlib, X Protocol, ICCCM,
- XLFD, 2nd Ed., Digital Press, Bedford, MA, 1990. ISBN (Digital Press)
- 1-5558-050-5, (Prentice-Hall) 0-13-972050-2.
- Table 1
- LOCATION MACHINE INTERNET ANONYMOUS
- NAME ADDRESS FTP DIRECTORY
- Western US gatekeeper.dec.com 16.1.0.2 pub/X11/R4
- Central US mordred. cs.purdue. edu 128.10.2.2 pub/X11/R4
-
- Central US giza. cis.ohio-state.edu 128.146.8.62 pub/X.VllR4
- Southeast US uunet. uu. net 192.48.96.2 XR4
-
- Northeast US crl.dec.com 192.58.206.2 pub/Xll/R4
-
- UK Janet src.doc. ic. ac. uk 129.31.81.36 X.V11R4
- UK niftp uk. ac. ic. doc. src <XV11R4>*
-
- Australia munnari.oz.au 128.250.1.21 X.V11/R4
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Image Processing
-
-
- Part 1: Reading The Tag Image File Format
-
-
-
-
- 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
-
-
- Programmers increasingly must obtain images, manipulate them, improve them,
- and output them. This article is the first in a series on images and image
- processing. Each article will include source code to demonstrate the concepts
- it covers. The code implements the C Image Processing System (CIPS), a small
- system that combines image processing operators with a simple user interface.
- The first article of the series discusses image input and the Tag Image File
- Format (TIFF). The second and third articles will discuss image output --
- displaying and printing images. The fourth article will discuss histograms and
- histogram equalization. [These articles will not all appear in sequential
- issues. -- pjp]
- Image processing involves processing or altering an existing image in a
- desired manner. The first step is obtaining an image. While this may sound
- obvious, it is not a simple matter, since usable image data is not readily
- available. The programmer needs a simple method of obtaining image data in a
- standard, usable format. The Tag Image File Format was designed to promote the
- interchange of digital image data.[1]
- Aldus (of PageMaker fame), Microsoft, and several other computer and scanner
- companies decided to write an industry standard for digital image data
- communication. Their collaboration resulted in the TIFF specification. Since
- most scanner manufacturers support the standard in their PC and Macintosh
- products, TIFF is a natural for PC-based image processing.
- The goals of the TIFF specification are extensibility, portability, and
- revisability. TIFF must be extensible in the future. TIFF must be able to
- adapt to new types of images and data and must be portable between different
- computers, processors, and operating systems. TIFF must be revisable -- it is
- not a read-only format. Software systems should be able to edit, process, and
- change TIFF files.
- The tag in Tag Image File Format refers to the file's basic structure. A TIFF
- tag provides information about the image, such as its width, length, and
- number of pixels. Tags are organized in tag directories. Tag directories have
- no set length or number, since pointers lead from one directory to another.
- The result is a flexible file format that can grow and survive in the future.
- Figure 3 contains the existing standard tags.
-
-
- Image Data Basics
-
-
- An image consists of a two-dimensional array of numbers. The color or gray
- shade displayed for a given picture element (pixel) depends on the number
- stored in the array for that pixel. The simplest type of image data is black
- and white. It is a binary image since each pixel is either 0 or 1. You should
- scan line drawings as binary images because you will save storage space and
- because you will not lose any information in the process.
- The next more complex type of image data is grayscale, where each pixel takes
- on a value between 0 and the number of gray scales or gray levels that the
- scanner can record. These images appear like common black-and-white
- photographs -- they are black, white, and shades of gray. Some scanners record
- only 16 shades of gray, sufficient for simple applications. In more serious
- work, however, you should try to use scanners that produce 256 shades of gray.
- Since the average person can distinguish about 40 shades of gray, a 16-shade
- image may appear rough, while a 256-shade image "looks like a photograph."
- This series of articles will concentrate on grayscale images.
- The most complex type of image is color. Color images are similar to grayscale
- except that there are three bands, or channels, corresponding to the additive
- primary colors red, green, and blue. Thus, each pixel has three values
- associated with it. A color scanner uses red, green, and blue filters to
- produce those values.
- You can obtain TIFF images by using a desktop scanner and your own
- photographs. Color photos work reasonably well, but black-and-white photos are
- better. I have used the Hewlett-Packard ScanJet and ScanJet Plus with good
- results. The code disk for this issue contains several TIFF images. (See page
- 80 to order. --Ed.) The ScanJet is limited to 16 shades of gray. The ScanJet
- Plus gives up to 256 shades of gray. Buying your own scanner (about $1,500) is
- not necessary since many printers and copy services now support desktop
- publishing with scanners. They will typically charge $10 to $20 per scan. Some
- PC dealers sell scanners and will also provide this service.
- Be prepared to fail on your first try at scanning photos. The biggest problem
- is file size. The scanner can scan 300 dots per inch, so a 3x5 photo at 300
- dpi provides 900x1500 pixels. At eight bits per pixel (256 shades of gray) the
- image file comes to over 1,350,000 bytes. This is not practical for many
- applications. Instead, select a one- or two-inch square in your photo and work
- with it. The scanning software allows you to preview the scanned photo and to
- select just the area you want scanned onto disk, the resolution in dots per
- inch, and the number of gray shades.
-
-
- TIFF Specifics
-
-
- Figure 1 (a) shows the structure of TIFF. The first eight bytes of the file
- are the header. These eight bytes have the same format on all TIFF files. They
- are the only items set in concrete for TIFF files. The remainder of the file
- differs from image to image. Figure 1 (b) shows the IFD or Image File
- Directory, which contains the number of directory entries and the directory
- entries themselves. Figure 1 (c) shows the structure of each directory entry.
- Each entry contains a tag indicating what type of information the file holds,
- the data type of the information, the length of the information, and a pointer
- to the information or the information itself.
- Figure 2 shows the beginning of a TIFF file. The addresses are located on the
- left side in decimal, and the bytes and their valves are in the table in hex.
- Glancing between Figure 1 and Figure 2 should clarify the structure. The first
- eight bytes are the header. Bytes zero and one tell whether the file stores
- numbers most significant byte (MSB) or least significant byte (LSB) first. If
- bytes zero and one are II (0x4949), then the least significant byte is first
- (predominant in the PC world). If the value is MM (0x4D4D), then the most
- significant byte is first (predominant in the Macintosh world). Your software
- needs to read both formats.
- The example in Figure 2 shows LSB first. Bytes two and three give the TIFF
- version number, which should be 42 (0x2A) in all TIFF images. Bytes four to
- seven give the offset to the first image file directory (IFD). Note that all
- offsets in TIFF indicate locations with respect to the beginning of the file.
- The first byte in the file has the offset 0. The offset in Figure 2 is 8, so
- the IFD begins in the ninth byte of the file.
-
-
- The IFD
-
-
- The content of address eight is 27, indicating that this file has 27 12-byte
- directory entries. The first two bytes of the entry contain the tag, which
- tells the type of information the entry contains. The directory entry at
- location zero (Figure 2) contains tag=255. This tag tells the file type.
- (Refer to Figure 3 for possible tags.) The next two bytes of the entry give
- the data type of the information (Figure 4 lists the possible data types and
- their lengths). Directory entry zero in Figure 2 is type 3, a short (two-byte
- unsigned integer). The next four bytes of the entry give the length of the
- information. This length is not in bytes, but rather in multiples of the data
- type. If the data type is a short and the length is 1, then the length is one
- short, or two bytes. An entry's final four bytes give either the value of the
- information or a pointer to the value. If the size of the information is four
- bytes or less, then the information is stored here. If it is longer than four
- bytes, then a pointer to it is stored. The information in directory entry zero
- is two bytes long and is stored here with a value of 1. (This value has no
- meaning for this tag.)
- As for the next two entries, the first entry has tag=256. This is the image
- width of the image in number of columns. The type is short and the length of
- the value is one short, or two bytes. The value 600 means that there are 600
- columns in the image. The second entry has tag=257. This is the image length
- or height in number of rows. The type is short, the length is 1, and the value
- is 602, meaning that the image has 602 rows.
- You continue through the directory entries until you reach the offset to the
- next IFD. If this offset is 0, as in Figure 2, then no more IFDs follow in the
- file.
-
-
- The Code
-
-
- The code in Listing 1, Listing 2, and Listing 3 read image data from a TIFF
- file into a 100x100 array of shorts. The code can read four or eight bit gray
- scale data either most significant byte first or least significant byte first.
- Future articles will build CIPS (the C Image Processing System) on top of
- these routines.
-
- Listing 1 (cips.h) contains the #include files and the data structures. The
- structure tiff_header_struct holds the essential tags we must extract from the
- TIFF header.
- The function read_tiff_header in Listing 2 first determines whether the file
- uses LSB first or MSB first since the method used influences the manner in
- which the functions extract_long_from_buffer and extract_short_from_buffer
- read the remainder of the file header. Next, the offset to the Image File
- Directory is read. The next section seeks to the IFD and reads the entry
- count, or number of entries in the IFD. Finally, the code loops over the
- number of entries. It reads each entry and picks out the necessary tags. The
- essential information is the width and length of the image, the bits per pixel
- (four-bit or eight-bit data), and the offset to the start of the data.
- The function read_tiff_image in Listing 3 uses read_tiff_header and the header
- information to read data into a 100x100 array of shorts. The code seeks to the
- beginning of the data and then to the first line to read. The code in the for
- loop seeks to the first element on the line, reads the line, and seeks to the
- end of the line. Each seek depends on the number of bits per pixel. If the
- file uses eight bits per pixel, then the seeks are integer multiples of the
- number of pixels. If the files uses four bits per pixel, the seeks are half as
- long. The function read_line reads the image data into a buffer, then places
- the data into the array of shorts. read_line uses unions defined in cips.h and
- also depends on the number of bits per pixel.
- The next article in this series will introduce the framework for the C Image
- Processing System and describe how to display images.
- References
- 1. TIFF Developer's Toolkit, Aldus Corporation, 1988.
- Figure 1. Structure of the Tag Image File Format.
- Figure 2
- address contents
- (decimal) (hex)
- header
- 0 49 49
- 2 2A 00
- 4 08 00 00 00
-
- IFD
- 8 1B 00
- 0th directory entry
- 10 FF 00 tag=255
- 12 03 00 type=3 (short)
- 14 01 00 00 00 length=1
- 18 01 00 00 00 value=1
-
- 1rst directory entry
- 22 00 01 tag=256
- 24 03 00 type=3 (short)
- 26 01 00 00 00 length=1
- 30 58 02 00 00 value=600
- 2nd directory entry
- 34 01 01 tag=257
- 36 03 00 type=3 (short)
- 38 01 00 00 00 length=1
- 42 5A 02 00 00 value=602
- .
- .
- .
- offset to next IFD
- 334 00 00 00 00
- offset=0 so there are no more IFD's
- Figure 3
- SubfileType
- Tag = 255 (FF) Type = short N = 1
- Indicates the kind of data in the subfile.
- -----------------------------------------------------------------------------
- ImageWidth
- Tag = 256 (100) Type = short N = 1
- The width (x or horizontal) of the image in pixels.
- -----------------------------------------------------------------------------
- ImageLength
- Tag = 257 (101) Type = short N = 1
- The length (y or height or vertical) of the image in pixels.
- -----------------------------------------------------------------------------
- RowsPerStrip
- Tag = 278 (116) Type = long N = 1
- The number of rows per strip. The default is the entire image in one strip.
- -----------------------------------------------------------------------------
- StripOffsets
- Tag = 273 (111) Type = short or long N = strips per image
- The byte offset for each strip.
-
- -----------------------------------------------------------------------------
- StripByteCounts
- Tag = 279 (117) Type = long N = 1
- The number of bytes in each strip.
- -----------------------------------------------------------------------------
- SamplesPerPixel
- Tag = 277 (115) Type = short N = 1
- The number of samples per pixel (1 for monochrome data, 3 for color).
- -----------------------------------------------------------------------------
- BitsPerSample
- Tag = 258 (102) Type = short N = SamplesPerPixel
- The number of bits per pixel. 2**BitsPerSample = # of gray levels.
- -----------------------------------------------------------------------------
- Figure 4
- Type Length of the Type
- ----------------------------------------------------
- 1 = byte 8 bit unsigned integer
- 2 = ASCII 8 bit bytes that store ASCII codes
- (the last byte must be null)
- 3 = short 16 bit (2 byte) unsigned integer
- 4 = long 32 bit (4 byte) unsigned integer
- 5 = rational Two longs: The first is the numerator,
- the second is the denominator
-
- Listing 1 (cips.h)
- /*********************************************
- * file d:\cips\cips.h
- *
- * Functions: This file contains no functions.
- * It contains declarations of the data structures
- * used by the C Image Processing Systems CIPS.
- *
- * Purpose: To declare data structures.
- *
- * Modifications: created June 1990
- **********************************************/
-
- #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"
-
- #define MAX_NAME_LENGTH 80
- #define ROWS 100
- #define COLS 100
- #define GRAY_LEVELS 255
-
- /**********************************************
- * The following struct defines the information
- * you need to read from the tiff file
- * header.
- **********************************************/
-
- struct tiff_header_struct{
- short lsb;
-
- long bits_per_pixel;
- long image_length;
- long image_width;
- long strip_offset;
- };
-
- /****************************************
- * The following four unions are used
- * to put the bytes from the header
- * into either an integer or a floating
- * point number.
- ****************************************/
-
- union short_char_union{
- short s_num;
- char s_alpha[2];
- };
-
- union int_char_union{
- int i_num;
- char i_alpha[2];
- };
-
- union long_char_union{
- long l_num;
- char l_alpha[4];
- };
-
- union float_char_union{
- float f_num;
- char f_alpha[4];
- };
-
-
- Listing 2 (tiff.c)
- /***********************************************
- * file d:\cips\tiff.c
- *
- * Functions: This file contains
- * read_tiff_header
- * extract_long_from_buffer
- * extract_short_from_buffer
- *
- * Purpose: This file contains the subroutines
- * that read the tiff files header information.
- *
- * External Calls:
- * mof.c - my_open_file
- * mrw.c - my_read
- *
- * Modifications: created 23 June 1990
- ***********************************************/
-
- #include "d:\cips\cips.h"
-
- read_tiff_header(file_name, image_header)
- char file_name[];
- struct tiff_header_struct *image_header;
- {
-
- char buffer[12];
-
- int bytes_read,
- closed,
- file_desc,
- i,
- j,
- lsb,
- not_finished;
-
- long bits_per_pixel,
- image_length,
- image_width,
- length_of_field,
- offset_to_ifd,
- position,
- strip_offset,
- subfile,
- value;
-
- short entry_count,
- field_type,
- s_bits_per_pixel,
- s_image_length,
- s_image_width,
- s_strip_offset,
- tag_type;
-
- file_desc = my_open(file_name);
-
- /*************************************
- * Determine if the file uses MSB
- * first or LSB first
- *************************************/
-
- bytes_read = my_read(file_desc, buffer, 8);
-
- if(buffer[0] == 0x49)
- lsb = 1;
- else
- lsb = 0;
-
- /*************************************
- * Read the offset to the IFD
- *************************************/
-
- extract_long_from_buffer(buffer, lsb, 4,
- &offset_to_ifd);
-
- not_finished = 1;
- while(not_finished){
-
- /************************************
- * Seek to the IFD and read the
- * entry_count, i.e. the number of
- * entries in the IFD.
- *************************************/
-
- position = lseek(file_desc, offset_to_ifd, 0);
-
- bytes_read = my_read(file_desc, buffer, 2);
- extract_short_from_buffer(buffer, lsb, 0,
- &entry_count);
-
- /***************************************
- * Now loop over the directory entries.
- * Look only for the tags we need. These are:
- * ImageLength
- * ImageWidth
- * BitsPerPixel(BitsPerSample)
- * StripOffset
- ***************************************/
-
- for(i=0; i<entry_count; i++){
- bytes_read = my_read(file_desc, buffer, 12);
- extract_short_from_buffer(buffer, lsb, 0,
- &tag_type);
-
- switch(tag_type){
- case 255: /* Subfile Type */
- extract_short_from_buffer(buffer, lsb, 2,
- &field_type);
- extract_short_from_buffer(buffer, lsb, 4,
- &length_of_field);
- extract_long_from_buffer(buffer, lsb, 8,
- &subfile);
- break;
-
- case 256: /* ImageWidth */
- extract_short_from_buffer(buffer, lsb, 2,
- &field_type);
- extract_short_from_buffer(buffer, lsb, 4,
- &length_of_field);
- extract_long_from_buffer(buffer, lsb, 8,
- &subfile);
- break;
- case 256: /* ImageWidth */
- extract_short_from_buffer(buffer, lsb, 2,
- &field_type);
- extract_short_from_buffer(buffer, lsb, 4,
- &length_of_field);
- if(field_type == 3){
- extract_short_from_buffer(buffer, lsb, 8,
- &s_image_width);
- image_width = s_image_width;
- }
- else
- extract_long_from_buffer(buffer, lsb, 8,
- &image_width);
- break;
- case 257: /* ImageLength */
- extract_short_from_buffer(buffer, lsb, 2,
- &field_type);
- extract_short_from_buffer(buffer, lsb, 4,
- &length of_field);
- if(field_type == 3){
- extract_short_from_buffer(buffer, lsb, 8,
- &s_image_length);
- image_length = s_image_length;
-
- }
- else
- extract_long_from_buffer(buffer, lsb, 8,
- &image_length);
- break;
- case 258: /* BitsPerPixel */
- extract_short_from_buffer(buffer, lsb, 2,
- &field_type);
- extract_short_from_buffer(buffer, lsb, 4,
- &length_of_field);
- if(field_type == 3){
- extract_short_from_buffer(buffer, lsb, 8,
- &s_bits_per_pixel);
- bits_per_pixel = s_bits_per_pixel;
- }
- else
- extract_long_from_buffer(buffer, lsb, 8,
- &bits_per_pixel);
- break;
-
- case 273: /* StripOffset */
- extract_short_from_buffer(buffer, lsb, 2,
- &field_type);
- extract_short_from_buffer(buffer, lsb, 4,
- &length_of_field);
- if(field_type == 3){
- extract_short_from_buffer(buffer, lsb, 8,
- &s_strip_offset);
- strip_offset = s_strip_offset;
- }
- else
- extract_long_from_buffer(buffer, lsb, 8,
- &strip_offset);
- break;
-
- default:
- break;
-
- } /* ends switch tag_type */
- } /* ends loop over i directory entries */
-
- bytes_read = my_read(file_desc, buffer, 4);
- extract_long_from_buffer(buffer, lsb, 0,
- &offset_to_ifd);
- if(offset_to_ifd == 0) not_finished = 0;
-
- } /* ends while not_finished */
-
- image_header->lsb = lsb;
- image_header->bits_per_pixel = bits_per_pixel;
- image_header->image_length = image_length;
- image_header->image_width = image_width;
- image_header->strip_offset = strip_offset;
-
- closed = close(file_desc);
-
- } /* ends read_tiff_header */
-
- /****************************************
-
- * extract_long_from_buffer(...
- *
- * This takes a four byte long out of a
- * buffer of characters. It is important
- * to know the byte order LSB or MSB.
- ****************************************/
-
- extract_long_from_buffer(buffer, lsb, start, number)
- char buffer[];
- int lsb, start;
- long *number;
- {
- int i;
- union long_char_union lcu;
-
- if(lsb == 1){
- lcu.l_alpha[0] = buffer[start+0];
- lcu.l_alpha[1] = buffer[start+1];
- lcu.l_alpha[2] = buffer[start+2];
- lcu.l_alpha[3] = buffer[start+3];
- } /* ends if lsb = 1 */
-
- if(lsb == 0){
- lcu.l_alpha[0] = buffer[start+3];
- lcu.l_alpha[1] = buffer[start+2];
- lcu.l_alpha[2] = buffer[start+1];
- lcu.l_alpha[3] = buffer[start+0];
- } /* ends if lsb = 0 */
-
- *number = lcu.l_num;
-
- } /* ends extract_long_from_buffer */
-
- /****************************************
- * extract_short_from_buffer(...
- *
- * This takes a two byte short out of a
- * buffer of characters. It is important
- * to know the byte order LSB or MSB.
- ****************************************/
-
- extract_short_from_buffer(buffer, lsb, start, number)
- char buffer[];
- int lsb, start;
- short *number;
- {
-
- int i;
- union short_char_union_lcu;
-
- if(lsb == 1){
- lcu.s_alpha[0] = buffer[start+0];
- lcu.s_alpha[1] = buffer[start+1];
- } /* ends if lsb = 1 */
-
- if(lsb == 0){
- lcu.s_alpha[0] = buffer[start+1];
- lcu.s_alpha[1] = buffer[start+0];
- } /* ends if lsb = 0 */
-
-
- *number = lcu.s_num;
-
- } /* ends extract_short_from_buffer */
-
-
- LISTING 3 (rtiff.c)
- /*********************************************
- * file d:\cips\rtiff.c
- *
- * Functions: This file contains
- * read_tiff_image
- * read_line
- * seek_to_first line
- * seek_to_end_of_line
- *
- * Purpose: These functions read a TIFF image
- * and insert the data into a ROWSxCOLS array
- * of short.
- *
- * External Calls:
- * mof.c - my_open
- * mrw.c - my_read
- * tiff.c - read_tiff_header
- *
- * Modifications: created 25 June 1990
- *********************************************/
-
- #include "d:\cips\cips.h"
-
- read_tiff_image(image_file_name, array, il, ie, ll, le)
- char image_file_name[];
- int il, ie, ll, le;
- short array[ROWS] [COLS];
- {
- char buffer[100],
- rep[80];
- int bytes_read,
- closed,
- file_descriptor,
- i;
- float a;
- long line_length, offset;
- unsigned long position;
- struct tiff_header_struct image_header;
- read_tiff_header(image_file_name, &image_header);
-
- /****************************************************
- * Procedure:
- * Seek to the strip offset where the data begins.
- * Seek to the first line you want.
- * Loop over the lines you want to read:
- * Seek to the first element of the line.
- * Read the line.
- * Seek to the end of the data in that line.
- ****************************************************/
-
- file_descriptor = my_open(image_file_name);
- position = lseek(file_descriptor,
-
- image_header.strip_offset, 0);
- position = seek_to_first_line(file_descriptor,
- &image_header, il);
-
- for(i=0; i<(ll-il); i++){
- offset = (ie-1)/(8/image_header.bits_per_pixel);
- position = lseek(file_descriptor, offset, 1);
- bytes_read = read_line(file_descriptor, array, i,
- &image_header, ie, le);
- position = seek_to_end_of_line(file_descriptor,
- le, &image_header);
- position = lseek(file_descriptor, 1, 1); /*???*/
- } /* ends loop over i */
-
- closed = close(file_descriptor);
-
- } /* ends read_tiff_image */
-
- /*******************************************************
- * read_line(...
- *
- * This function reads bytes from the TIFF file into
- * a buffer, extracts the numbers from that buffer,
- * and puts them into a 100x100 array of shorts.
- * The process depends on the number of bits per
- * pixel used in the file (4 or 8).
- *******************************************************/
-
- read_line(file_descriptor, array, line_number,
- image_header, ie, le)
- int file_descriptor, ie, le, line_number;
- short array[ROWS][COLS];
- struct tiff_header_struct *image_header;
-
- {
- char buffer[100], first, second;
- float a, b;
- int bytes_read, i;
- unsigned int bytes_to_read;
- union short_char_union scu;
-
- for(i=0; i<100; i++)
- buffer[i] = '\0';
-
- /**********************************************
- * Use the number of bits per pixel to calculate
- * how many bytes to read.
- *************************************************/
-
- bytes_to_read = (le-ie)/(8/image_header->bits_per_pixel);
- bytes_read = read(file_descriptor, buffer,
- bytes_to_read);
-
- for(i=0; i<bytes_read; i++){
-
- /**********************************************
- * Use unions defined in cips.h to stuff bytes
- * into shorts.
- ************************************************/
-
-
- if(image_header->bits_per_pexil == 8){
- scu.s_num = 0;
- scu.s_alpha[0] = buffer[i];
- array[line_number] [i] = scu.s_num;
- } /* ends if bits_per_pixel == 8 */
-
- if(image_header->bits_per_pixel == 4){
-
- scu.s_num = 0;
- second = buffer[i] & 0X000F;
- scu.s_alpha[0] = second;
- array[line_number] [i*2+1] = scu.s_num;
-
- scu.s_num = 0;
- first = buffer[i] >> 4;
- first = first & 0x000F;
- scu.s_alpha[0] = first;
- array[line_number] [i*2] = scu.s_num;
-
- } /* ends if bits_per_pixel == 4 */
- } /* ends loop over i */
-
- return(bytes_read);
- } /* ends read_line */
-
- /*****************************************
- * seek_to_first_line(...
- ******************************************/
-
- seek_to_first_line(file_descriptor, image_header, il)
- int file_descriptor, il;
- struct tiff_header_struct *image_header;
- {
- long offset;
- unsigned long position;
-
- offset = (il-1)*image_header->image_width/
- (8/image_header->bits_per_pixel);
- /* seek from current position */
- position = lseek(file_descriptor, offset, 1);
- return(position);
- ) /* ends seek_to_first_line */
-
- /*****************************************
- * seek_to_end_of_line(...
- *******************************************/
-
- seek_to_end_of_line(file_descriptor, le, image_header)
- int file_descriptor, le;
- struct tiff_header_struct *image_header;
- {
- int origin;
- long offset;
- unsigned long position;
-
- offset = (image_header->image_width-le)/
- (8/image_header->bits_per_pixel);
- origin = 1; /* seek from the current position */
-
- position = lseek(file_descriptor, offset, origin);
- return(position);
- } /* ends seek_to_end_of_line */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Standard C
-
-
- The Header <locale.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 Standard C, which he co-authored with Jim
- Brodie. You can reach him at pjp@plauger. uunet.
-
-
-
-
- History
-
-
- The header <locale.h> is an invention of X3J11, the committee that developed
- the C standard. You will find little that resembles locales in earlier
- implementations of C. That stands at odds with the committee's stated purpose,
- "to codify existing practice." Nevertheless, those of us active within X3Jll
- at that time felt we were acting out of the best of motives -- self defense.
- This particular header popped up about five years after work began on the
- standard. At that time, many of us felt that the standard was essentially
- complete. We were simply putting a few finishing touches on a product in which
- we had invested five years of our lives. Resistance was mounting to change of
- any sort.
- I recall mentioning a change that I would have liked. (I forget now just what
- the change was.) In the interest of speeding closure, however, I suggested
- that the committee not make the change. An attendee from the UK, Keith Winter,
- also expressed support for the change. But he, too, was willing to let it
- slide. After all, he said, it was just one of many changes that would have to
- be made to the ISO standard for C.
- Silence.
- After we collected our collective wits, several of us simultaneously uttered
- the moral equivalent of, "Say what?" Winter went on to explain calmly that a
- number of Europeans were unhappy with certain parts of the C standard being
- developed by X3J11. It was simply too American in several critical ways. They
- despaired of trying to educate us insular Yanks about the needs of the world
- marketplace. Rather, they were content to wait and fight their battles on a
- more congenial field. The Europeans took it for granted that an ISO standard
- for C must differ from the ANSI standard.
- Many of us disagreed with that position. We felt it imperative that whatever
- standard ANSI developed had to be acceptable to the international community.
- We had seen the effects in the past of computer language standards that
- differed around the world. Our five years of effort would be in vain, we felt,
- if the final word on C came from a separate committee second-guessing all our
- decisions.
- So we sighed a deep sigh and asked the Europeans to show us their shopping
- list of changes. Most of the items on the list dealt with ways to adapt C
- programs to different cultures. That is a much more obvious problem in a land
- of many languages and nations such as Europe. Americans enjoy the luxury of a
- single (official) language and a fairly simple alphabet.
- AT&T Bell Labs went so far as to host a special meeting to deal with various
- issues of internationalization. (This is a big word that people are uttering
- more and more often. It seems to have no acceptable synonym that is any
- shorter. The techie solution is to introduce the barbarism I18N, pronounced
- EYE eighteen EN. The 18 stands for the number of letters omitted.) Out of that
- meeting came the proposal for adding locale support to Standard C. The
- machinery eventually adopted is remarkably close to the original proposal.
- Adding locales to C had the desired effect. Many of the objections to ANSI C
- as an international standard were derailed. It cost X3Jll an extra year, by my
- estimation, to hammer out locales. And we probably spent yet another year
- dealing with residual gripes from the international community. (And WG14, the
- ISO C standard committee, is still working on additions to the existing
- Standard.) Nevertheless, we succeeded in producing a standard for C that is
- currently identical at both ANSI and ISO levels.
-
-
- Before Locales
-
-
- Writing adaptive code is not entirely new. An early form sprung up about
- fifteen years ago in the UNIX operating system. Folks got the idea of adding
- environment variables to the system call that launches new processes. (That
- service is called exec, or some variant thereof, in UNIX land.) Environment
- variables are an open-ended set of names, each of which identifies a
- null-terminated string that represents its value. You can add, alter, or
- delete environment variables in a process. Should that process launch another
- process, the environment variables are automatically copied into the image of
- the new guy.
- The new process can simply ignore environment variables. It loses a few dozen,
- or a few hundred, bytes of storage that it might otherwise enjoy. Or it can
- look for certain environment variables and study their current values. A
- common variable is TZ, which provides information to the library date
- functions about the current time zone. If the value of TZ is, say, EST05E0T,
- the time functions know to label local standard time as EST and local daylight
- savings time as EDT. The local (standard) time zone is five hours later than
- UTC, known in the past as Greenwich Mean Time.
- Environment variables have many uses. They are a great way to smuggle file
- names into an application program. It is almost always a bad idea to wire file
- names directly into a program. Prompting the user for file names is mostly a
- good idea, except for secret files about which the user should not have to be
- informed. Asking for such a file name on the command line that starts the
- program is somewhat better, but it can be a nuisance. It is a particular
- nuisance is several programs in a suite need access to the same file name.
- That's why it is often much nicer to set an environment variable to the file
- name once and for all in a script that starts a session. The file name is
- captured in one place, but is made available to a whole hierarchy of programs.
- If you are at all literate about MS-DOS, you probably know that that system
- supports environment variables too. They are just one of many good ideas
- borrowed from past experience with UNIX. I have purchased several bits of
- commercial software that use environment variables to advantage. A common use
- is to locate special directories that contain support files or that are
- well-suited for hosting temporary files. But they have many other uses as
- well.
- The Standard C library includes the function getenv. You will find it declared
- in the standard header <stdlib.h>. Call getenv with the name of an environment
- variable and it will return a pointer to its value string, if there is one. It
- is not considered an error to reference a variable that is not defined.
- Note, however, that the Standard does not include setenv, the usual companion
- to getenv. That is the common name for the function that lets you alter the
- values associated with environment variables. Simply put, committee X3Jll
- couldn't decide how to describe the semantics of setenv. They differ too much
- among various single-user and multiprocessing systems. So you can write
- portable code that reads environment variables, but you can't alter them in a
- standard way.
-
-
- Why Locales?
-
-
- What do locales provide that environment variables do not? In a word,
- structure. This is the era of object-oriented hoopla. So you can look on
- locales, if you wish, as object-oriented environment variables. A single
- locale provides information on many related parameters. The values are
- consistent for a given culture. You would have to pump dozens of reserved
- names into the name space for environment variables to transmit the same
- amount of information. And you run a greater risk that subsets of the
- information get altered inconsistently.
- When I talk about a culture, by the way, I don't mean just a group that speaks
- a common language. People in the USA write dates as 7/4/1776 (Independence
- Day). The same day in the UK is written as 4/7/1776 (Thanksgiving Day). Even
- within the USA, practices can vary. Where we civilians might write a debit as
- $-123.45, an accountant may well prefer (123.45).
- For this reason, and others, locales have substructure. You can set an entire
- locale, or you can alter one or more categories. Separate categories exist for
- controlling collation sequences, character classification, monetary
- formatting, other numeric formatting, and times. The header <locale.h> defines
- several macros with names such as LC_COLLATE and LC_TIME. Each expands to an
- integer value that you can use as the category argument to setlocale, the
- function that alters locales. An implementation can choose to provide
- additional categories as well. A program that uses such added categories will,
- of course, be less portable than one that does not.
- The idea behind categories is that an application may wish to tailor its
- locale. It may want to print dates in the local language and by the formatting
- rules of that language. But it may still opt to use the dot for a decimal
- point even though speakers of that language customarily write a comma. Or the
- application may adapt completely to a given locale, then change the monetary
- category to match a worldwide corporate standard for expressing accounting
- information.
- Much of the information provided in a locale is purely informative. C has
- never treated currency amounts as a special data type. It is no surprise,
- therefore, that the Standard C library is unaffected by a change in the
- monetary category. On the other hand, some changes in locale very definitely
- affect how certain library functions behave. If a culture uses a comma for a
- decimal point, then the scanf family should accept commas and the printf
- family (and strtod) should produce commas in the proper places. That is indeed
- what happens.
- Here are all the places where library behavior changes with locale:
- The functions strcoll and strxfrm in <string.h> can change how they collate
- when category LC_COLLATE changes.
- The functions in <ctype.h>, the printf and scanf families, and the numeric
- conversion functions in <stdlib.h>, can change how they test and alter certain
- characters when category LC_CTYPE changes.
- The multibyte character functions in <stdlib.h>, and the printf and scanf
- families, can change how they parse and translate multibyte strings when
- category LC_CTYPE changes.
- The printf and scanf families, and atof and strtod in <stdlib.h>, can change
- what they use for the decimal point character when category LC_NUMERIC
- changes.
- The strftime function in <time.h> can change how it converts times to
- character strings when category LC_TIME changes.
- The localeconv function in <locale.h> can change the information reported for
- the current locale when categories LC_MONETARY or LC_NUMERIC change.
-
-
-
- Using Locales
-
-
- If you are half as nervous as I am, this litany of changes should scare you.
- How do you write portable code if large chunks of the Standard C library can
- change behavior underfoot? Can you ship code to Germany and know what isalpha
- will do when it runs there? If you mix your code with functions from another
- source, how much trouble can they cause? Each time your functions get control,
- you may be running in a different locale. How do you code under those
- conditions?
- X3Jll anguished about such issues when we spelled out the behavior of locales.
- We recognized that many people don't want to be bothered with this machinery
- at all. Those folks should suffer little from the addition of locales. Still
- others have only modest goals. They want to trade in the Americanisms wired
- into older C for conventions more in tune with their culture. Still others are
- ambitious. They want to write code that can be sold unchanged, in binary form,
- in numerous markets. That code must be very sophisticated about changing
- locales.
- The simplest way to use locales is to ignore them. Every Standard C program
- starts up in the "C" locale. In this locale, the traditional library functions
- behave pretty much as they always have. islower returns a nonzero value only
- for the 26 letters of the English alphabet, for example. The decimal point is
- a dot. If your program never calls setlocale, none of this behavior can
- change.
- The next simplest way to use locales is to change once, just after program
- startup, and leave it at that. The C Standard requires no other locale names
- besides "C". But it does define a defalut locale designated by the empty
- string "". If your program executes
- setlocale (LC_ALL, "")
- it should shift to this default locale. Presumably, each implementation will
- devise a way to determine a default locale that pleases the locals. (An
- implementation that doesn't care a hoot about locales can make the default
- locale the same as the "C" locale, of course.)
- You must be more careful in using the library, once the locale can change on
- you. Some things get easier, such as displaying pretty dates or skipping the
- appropriate characters for white space. Other things get chancier, such as
- parsing strings with the functions in <ctype.h>. In a pinch, you can always
- revert part or all of the locale to the "C" locale, as in:
- char *s1 = setlocale(LC_CTYPE, "C");
- char *s2 = malloc(strlen(s1) + 1);
- if (s2 == NULL)
- <despair>
- strcpy(s2, s1);
- <use ctype functions safely>
- <setlocale(LC_ALL, s2);
- You can omit the business about copying the locale string returned by
- setlocale only if you are sure that no other calls to that function can
- intervene between the two shown above.
- I won't go into more sophisticated manipulation of locales at this point. That
- must wait until we have covered some of the implementation issues raised so
- far. You will find that they are dizzying enough in their own right.
-
-
- What the Standard Says
-
-
- If your primary goal is to query the current locale, you need to read at least
- two chunks of the Standard. The first provides an introduction to the standard
- header <locale.h>:
-
-
- 4.4 Localization <locale.h>
-
-
- The header <locale.h> declares two functions, one type, and defines several
- macros.
- The type is
- struct lconv
- which contains members related to the formatting of numeric values. The
- structure shall contain at least the following members, in any order. The
- semantics of the members and their normal ranges is explained in 4.4.2.1. In
- the "C" locale, the members shall have the values specified in the comments.
- char *decimal_point; /*"." */
- char *thousands_sep; /* "" */
- char *grouping; /* "" */
- char *int_curr_symbol; /* "" */
- char *currency_symbol; /* "" */
- char *mon_decimal_point; /* "" */
- char *mon_thousands_sep; /* "" */
- char *mon_grouping; /* "" */
- char *positive_sign; /* "" */
- char *negative_sign; /* "" */
- char int_frac_digits; /* CHAR_MAX */
- char frac_digits; /* CHAR_MAX */
- char p_cs_precedes; /* CHAR_MAX */
- char p_sep_by_space; /* CHAR_MAX */
- char n_cs_precedes; /* CHAR_MAX */
- char n_sep by_space; /* CHAR_MAX */
- char p_sign_posn; /* CHAR_MAX */
- char n_sign_posn; /* CHAR_MAX */
- The macros defined are NULL (described in 4.1.5); and
- LC_ALL
- LC_COLLATE
- LC_CTYPE
- LC_MONETARY
- LC_NUMERIC
-
- LC_TIME
- which expand to integral constant expressions with distinct values, suitable
- for use as the first argument to the setlocale function. Additional macro
- definitions, beginning with the characters LC_ and an upper-case letter,100
- may also be specified by the implementation. [end of excerpt]
- The second chunk you must read is the description of localeconv, the function
- that lets you query the current locale:
-
-
- 4.4.2 Numeric Formatting Convention Inquiry
-
-
-
-
- 4.4.2.1 The localeconv Function
-
-
-
-
- Synopsis
-
-
- #include <locale.h>
- struct lconv *localeconv(void);
-
-
- Description
-
-
- The localeconv function sets the components of an object with type struct
- lconv with values appropriate for the formatting of numeric quantities
- (monetary and otherwise) according to the rules of the current locale.
- The members of the structure with type char * are pointers to strings, any of
- which (except decimal_point) can point to "", to indicate that the value is
- not available in the current locale or is of zero length. The members with
- type char are non-negative numbers, any of which can be CHAR_MAX to indicate
- that the value is not available in the current locale. The members include the
- following:
- char *decimal_point
- The decimal-point character used to format non-monetary quantities.
- char *thousands_sep
- The character used to separate groups of digits before the decimal-point
- character in formatted non-monetary quantities.
- char *grouping
- A string whose elements indicate the size of each group of digits in formatted
- non-monetary quantities.
- char *int_curr_symbol
- The international currency symbol applicable to the current locale. The first
- three characters contain the alphabetic international currency symbol in
- accordance with those specified in ISO 4217 Codes for the Representation of
- Currency and Funds. The fourth character (immediately preceding the null
- character) is the character used to separate the international currency symbol
- from the monetary quantity.
- char *currency_symbol
- The local currency symbol applicable to the current locale.
- char *mon_decimal_point
- The decimal-point used to format monetary quantities.
- char *mon_thousands_sep
- The separator for groups of digits before the decimal-point in formatted
- monetary quantities.
- char *man_grouping
- A string whose elements indicate the size of each group of digits in formatted
- monetary quantities.
- char *positive_sign
- The string used to indicate a non-negative-valued formatted monetary quantity.
- char *negative_sign
- The string used to indicate a negative-valued formatted monetary quantity.
- char int_frac_digits
- The number of fractional digits (those after the decimal-point) to be
- displayed in a internationally formatted monetary quantity.
- char frac_digits
- The number of fractional digits (those after the decimal-point) to be
- displayed in a formatted monetary quantity.
- char p_cs_precedes
- Set to 1 or 0 if the currency_symbol respectively precedes or succeeds the
- value for a non-negative formatted monetary quantity.
- char p_sep_by_space
- Set to 1 or 0 if the currency_symbol respectively is or is not separated by a
- space from the value for a non-negative formatted monetary quantity.
- char n_cs_precedes
- Set to 1 or 0 if the currency_symbol respectively precedes or succeeds the
- value for a negative formatted monetary quantity.
- char n_sep_by_space
- Set to 1 or 0 if the currency_symbol respectively is or is not separated by a
- space from the value for a negative formatted monetary quantity.
-
- char p_sign_posn
- Set to a value indicating the positioning of the positive_sign for a
- non-negative formatted monetary quantity.
- char n_sign_posn
- Set to a value indicating the positioning of the negative_sign for a negative
- formatted monetary quantity.
- The elements of grouping and mon_grouping are interpreted according to the
- following:
- CHAR_MAX No further grouping is to be performed.
- 0 The previous element is to be repeatedly used for the remainder of the
- digits.
- other The integer value is the number of digits that comprise the current
- group. The next element is examined to determine the size of the next group of
- digits before the current group.
- The value of p_sign_posn and n_sign_posn is interpreted according to the
- following:
- 0 Parentheses surround the quantity and currency_symbol.
- 1 The sign string precedes the quantity and currency_symbol.
- 2 The sign string succeeds the quantity and currency_symbol.
- 3 The sign string immediately precedes the currency_symbol.
- 4 The sign string immediately succeeds the currency_symbol.
- The implementation shall behave as if no library function calls the localeconv
- function.
-
-
- Returns
-
-
- The localeconv function returns a pointer to the filled-in object. The
- structure pointed to by the return value shall not be modified by the program,
- but may be overwritten by a subsequent call to the localeconv function. In
- addition, calls to the setlocale function with categories LC_ALL, LC_MONETARY,
- or LC_NUMERIC may overwrite the contents of the structure.
- Footnote:
- 100. See future library directions (4.13.3). [end of excerpt]
-
-
- Future Attractions
-
-
- Next month, I will discuss ways to implement locales. Since there is little or
- no history in this area, I can be particularly inventive. That makes locales
- particularly interesting to tinkerers like me. Internationalization is
- becoming more important. That should make the topic of interest to many of
- you. As for the rest of you, at least you can see how much trouble your code
- will encounter when everyone starts altering locales under foot.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Doctor C's Pointers(R)
-
-
- Puzzles, Part V
-
-
-
-
- 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 uunet!aussie!rex or aussie!rex@uunet.uu. net.
-
-
- This month we'll complete our quality of implementation set of puzzles. Try to
- debug them yourself first and see what messages your compiler produces.
-
-
- The Puzzles
-
-
- 1. I just know the @#$% file exists. See Listing 1.
- 2. When is a string literal not literal?
- /* 1*/#include <stdio.h>
-
- /* 3*/main ()
- /* 4*/{
- /* 5*/ printf("?? (Error:
- message) \n");
- /* 6*/}
- 3. Side-effects in unused arguments.
- /* l*/#include <stdio.h>
-
- /* 3*/main ()
- /* 4*/{
- /* 5*/ int i = 5, j = 6;
- /* 7*/ printf("Hello\n",
- ++i, j- -);
- /* 8*/}
- Output:
- Hello
- Are i and j incremented and decremented, respectively?
- 4. It compiles, it works, so what's the problem?
- /* 1*/#include <stdio.h>
-
- /* 3*/main()
- /* 4*/{
- /* 5*/ void f();
-
- /* 7*/ f(-32768);
- /* 8*/}
-
- /*10*/void f(i)
- /*11*/int i;
- /*12*/{
- /*13*/ printf("i = %d\n", i);
- /*14*/}
- 5. Hey, someone stole my loop!
-
- /* 1*/#include <stdio.h>
-
- /* 3*/main()
- /* 4*/{
- /* 5*/ int i;
-
- /* 7*/ for (i = 0; i < 5; ++i);
- /* 8*/ printf("i = %d\n", i);
- /* 9*/}
- Output:
- i = 5
- 6. I forgot to declare the formal parameter list but it doesn't seem to mind!
- /* 1*/double f(i, j)
- /* 2*/{
- /* 3*/ return i * j;
- /* 4*/}
-
-
- The Solutions
-
-
- 1. As indicated in the comment, this is a DOS-specific problem. A file does
- exist by the name of test.dat in the root directory of the default disk, but
- you can't seem to get at it. The message produced, however, does indicate some
- kind of problem. For some reason, the leading part of the filename is missing.
- On closer inspection you realize that not only is the t missing but so too is
- the \ and the two together mean something special in C.
- Of course, the compiler sees the filename as a tab character followed by
- est.dat. Why didn't the tab get displayed in the output? It did, but the tab
- stops of the terminal were set such that the tab was not obvious.
- The solution? Use \test.dat instead. Now the compiler sees no escape sequence.
- Note that according to ANSI C, a different rule is applied with the #include
- preprocessor directive. For example,
- #include "\test.h"
- causes the preprocessor to search for the file test.h in the root directory.
- The \t is not interpreted as a tab. The grammar of this directive is specific
- to the preprocessor -- the construct ". . ." must not be treated as a string
- literal. It is simply a string of arbitrary characters delimited by double
- quote characters. (ANSI C defines header names as quite separate tokens from
- string literals. The actual grammar used here is "xxx" where xxx is called a
- q-char-sequence.)
- 2. It is not unreasonable to expect the following output:
- ??(Error: message)
- However, ANSI C requires:
- [Error: message)
- According to PC-Lint you have stumbled on one of ANSI C's quiet changes," an
- instance where a correct program's behavior has been changed by the standard.
- line 5 - Trigraph Sequence \
- '??(' in literal (Quiet Change)
- I will not discuss trigraphs except to say that a trigraph is a
- three-character sequence beginning with ?? that permits certain punctuation
- characters to have an alternative representation. ANSI C invented trigraphs to
- allow C source to be mechanically converted to machines supporting the ISO-646
- character set (which can have alternate graphics for #, , [, [, ], and \, for
- example). Trigraphs are recognized before any tokens (such as string literals)
- are processed.
- 3. Yes. Before a function can be called, each of its arguments must be
- evaluated. Then their values are put into the function's call frame. A
- compiler is not required to know anything about any of the standard library
- functions (although it is permitted to), so provided the actual argument list
- is compatible with the function's prototype, the call is OK. In this case, the
- ellipsis notation is used in the printf prototype so there's no conflict.
- Interestingly, PC-Lint had the following to say:
- line 7 - number of arguments \
- inconsistent 'with format
- That is, it really did check the number and type of trailing arguments against
- the format string. This can be a very useful check, but it can only be
- performed if the first argument is a string literal. The inconsistency would
- not be detectable if the format argument were the name of a char array or a
- pointer initialized at runtime.
- 4. On the 16-bit compilers I tested, by far the most common output was
- i = -32768
- However, another result is possible:
- i = -1
- [You can even get 0 on some machine -- pjp]
- One compiler gave the following hints as to why:
- line 7 - Function f has no \
- prototype
- line 7 - Constant has long type
- line 10 - Parameter list for f \
- is inconsistent with previous call
- We know that on a 16-bit twos-complement machine, the smallest int value is
- -32768. As such, many people expect that is what we have passed to f.
- Certainly that's what f is expecting. Note, however, that the compiler warned
- that a long int was actually passed. Let's accept that for now and see what
- follows. We pass a 32-bit long, yet f expects a 16-bit int. You can pass the
- long in two ways -- low word first or high word first. Depending on which way
- the implementation chooses, f either maps into -32768 or -1.
- By adding a prototype in main for f, of the form
- /* 5*/ void f(int);
- the long int -32768 would be silently truncated to an int. Interestingly
- enough, this really would have the value -32768.
- Now, back to the type of -32768. In the April 1990 issue of CUJ (Volume 8,
- Number 8), I made the following statement: "An expression such as -32768
- consists of two source tokens; the unary minus operator and the integer
- constant 32768. Note there is no such thing as a negative constant in C. The
- constant is non-negative and it is preceded by a unary minus operator. An
- interesting situation exists on 16-bit twos-complement machines where -32768
- is the smallest value that can be stored in an int. It so happens that the
- type of -32768 when written in this form is not int; it's long int, but that's
- another story."
- This resulted in reader mail (and subsequent reply), but here is the
- explanation.
- According to the ANSI Standard, (page 28, lines 37-41), "The type of an
- integer constant is the first of the corresponding list in which its value can
- be represented. Unsuffixed decimal: int, long int, unsigned long int;
- unsuffixed octal or hexadecimal: int, unsigned int, long int, unsigned long
- int; suffixed by the letter u or U: unsigned int, unsigned long int; suffixed
- by the letter 1 or L: long int, unsigned long int; suffixed by both the
- letters u or U and 1 or L: unsigned long int."
- 32768 is an unsuffixed decimal, and it won't fit into an int. The compiler
- tries long and it works, so long is its type. The compiler then applies
- negation to the result of that long int. If you look carefully, you will see
- that the rules are different for decimal and octal/hex. The following program
- demonstrates this:
-
- #include <stdio.h>
- main() {
- printf("sizeof(-32768) = %lu\n",
- (unsigned long)sizeof(-32768));
- printf("sizeof(0x8000) = %lu\n",
- (unsigned long) sizeof(0x8000));
- printf("sizeof(0100000) = %lu\n",
- (unsigned long)sizeof(0100000));
- The values -32768, 0x8000, and 0100000 have exactly the same bit pattern when
- stored in 16 bits. However, the type of the first expression is long, while
- that of the second and third is unsigned int. The correct output produced is
- sizeof(-32768) = 4
- sizeof(0x8000) = 2
- sizeof(0100000) = 2
- 5. I see this kind of problem all the time, mostly with programmers new to C.
- When starting C, you learn that all statements must be terminated with a
- semicolon. However, if a while or for statement really is a statement, where
- does its semicolon go? The answer is "They don't have one, but each of their
- subordinate primitive statements does need one." In my introductory C
- textbook, I call for, while, if, etc., constructs rather than statements to
- avoid students putting in unneeded semicolons.
- The problem is that the language supports a null statement represented simply
- by a semicolon. As a result, spurious semicolons may be hazardous to your
- program, as in the previous example. The trailing semicolon on line 7
- represents a null statement that is subsequently taken as the body of the
- loop. The call to printf, therefore, occurs once, after the loop terminates.
- In this case, the output indicates the problem, but in cases where the actual
- loop body produces no visibly strange behavior, the problem can be difficult
- to find.
- PC-Lint did issue the following, very useful message:
- line 7 - Suspicious use of ;
- I didn't experiment to see just which uses of ; are not suspicious, but even
- if all null statements were flagged that would be useful, since null
- statements are not very commonly needed.
- 6. We all know that you must explicitly declare an identifier before using it.
- Of course, many rules have exeptions, and the obvious one here is for
- functions. If the compiler comes across a call to an unknown function, it
- presumes that function returns an int. It is not able to check the argument
- list because no prototype is available.
- There is one other, very obscure exception. If you omit the type from any
- formal arguments in a function definition, the compiler assumes that the
- argument has type int. The following old-style definitions are equivalent:
- double f(i, j)
- int i, j;
- {}
- double f(i, j)
- int i;
- int j;
- {}
- double f(i, j)
- int i;
- {}
- double f(i, j)
- int j;
- {}
- double f(i, j)
- {}
- The equivalent prototype version is:
- double f(int i, int j)
- {}
- However, you are not permitted to mix the old and new styles in certain ways.
- For example, the following are invalid:
- double f(i, int j)
- {}
- double f(int i, j)
- {}
- Having said all this, I strongly recommend you not use such default typing.
-
- Listing 1
- /* 1*/ #include <stdio.h> /* MS-DOS-specific */
-
- /* 3*/ main()
- /* 4*/ {
- /* 5*/ FILE *fp;
- /* 6*/ char filename[] = "\test.dat";
-
- /* 8*/ fp = fopen(filename, "r");
- /* 9*/ if (fp == NULL)
- /*10*/ printf ("Can't open file %s\n", filename);
- /*11*/ else {
- /*12*/ printf("File open\n");
- /*13*/ fclose(fp);
- /*14*/ }
-
- /*15*/ }
-
- Output:
-
- Can't open file est.dat
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Questions & Answers
-
-
- Readers' Replies
-
-
-
-
- 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) 493-4390. When you hear the answering message,
- press the * button on your telephone. Ken also receives email at
- kpugh@dukemvs.ac.duke.edu (Internet).
-
-
- Q
- Why do computer programmers confuse Halloween and Christmas?
- Thor Parrish
- Cambridge, MA
- A
- Let me think about that for a while. Check the end of the column. (KP)
-
-
- Reader's Replies
-
-
-
-
- Matching Braces
-
-
- In response to Kim Tsang about the closing braces in C programs, you can
- improve the readability of the code by defining the braces with #define at the
- top of the file to be BEGIN and END.
- Then they are easily found when scanning a listing and the comments at the end
- will help match them up. I also put the END in the same column as the BEGIN,
- then indent everything in between so that nothing should show up in that same
- column between the BEGIN and END, as shown in Listing 1.
- I can keep better track of my blocks and cut down on searching. (It also saves
- on colored pens.)
- I find that a four-character display works best for me. Any more and you crowd
- the right-hand side of the screen, any less reduces readability.
- Garrett J. Boni
- Baton Rouge, LA
- Thanks for another way of handling the blocking problem. I prefer braces, as
- they are only a keystroke apiece. (KP)
-
-
- Token Pasting
-
-
- Thank you for printing Matthias Hansen's reply to my problem from the July
- issue. His trick of using the token-pasting operator with the angle brackets
- was something I just didn't think of, and it should be useful in the future.
- Unfortunately, I can't get Herr Hansen's example to work. I tried Turbo C 2.0,
- Turbo C++ 1.0, and Microsoft C 6.0, and all report that they can't find the
- file MH_PRG.A. His variation for Quick C does work with all three and is
- similar to the solution I finally used.
- I've come up with some new solutions to the problem. First, if the
- name-dependent code has some regularity you can use the token-pasting and
- stringizing operators in a block of preprocessor code to produce what you
- need. This can easily become unreadable, though, and so is hard to maintain.
- Another solution would be to produce the code through an awk program, though
- it, too, requires some regularity. It will be easier to maintain. I did
- something like this years ago, and am still kicking myself for not remembering
- it.
- There is yet a third way to do this, one that can handle any arbitrary
- differences. In our main source file, we put the line
- #include "progxh.h"
- and in that file we put lines like
- #include "prog1_a.h"
- #include "prog1_b.h"
- The trick is to create this file only when needed. In your make file, before
- the compiler is called, add a line to create progxh.h. You can use the ECHO
- command in a simple batch file, or maybe an awk program if the file is
- complicated. A clever programmer could use a feature in some make
- implementations (like PolyMake) to create temporary files with the appropriate
- contents.
- None of the solutions we've discussed are clean; they all have the potential
- for developing portability and/or maintenance problems. Which one you use will
- depend upon the circumstances and whatever pay-now/pay-later philosophy you
- have.
- Jim Howell
- Lafayette, Colorado
- Jim's original question revolved around using the preprocessor to force a
- #include to use different source files, based on the value of a #define. (KP)
-
-
-
- qsort Function Prototypes
-
-
- This letter is in response to Firdaus Irani's question concerning calls to
- qsort (CUJ Dec. 1990, p. 92). I, too, had similar problems with qsort. Unlike
- Firdaus, I had better luck with Borland's technical support. I was told that
- the function was strictly prototyped to only accept const void * as arguments
- to _fcmp(). I don't remember exactly why he said it was this way. He did tell
- me how to use it correctly.
- Using the example Firdaus supplied I changed the prototype for comp to
- comp(const void *, const void *) and then changed the comp routine to look
- like this Listing 2.
- This compiles under Turbo C++. I tried the MicroSoft C compiler and it also
- compiled fine.
- This answers Firdaus'question of multiple calls to qsort with different
- compare functions. You prototype the functions with const void * and then cast
- the pointers to whatever type of pointer you need. Hopefully this will help
- others who have had the same problem.
- Mike Beard
- Abilene, Texas
- Thanks for your answer. For the efficiency-minded, you might put the function
- that calls qsort in its own source file. Do not include <string.h>, but simply
- have a prototype:
- int strcmp(const void *a, const void *b);
- Then you could call qsort with strcmp. This bypasses the prototype mechanism
- for the sake of efficiency. (KP)
- [This declaration violates the C standard, but you can probably get away with
- it on most implementations. -- PJP]
-
-
- Large Arrays In Turbo C
-
-
- I'm writing in reference to a question in your column in the November 1990
- issue of The C Users Journal. The question was by Abdel Hindi from Montreal,
- Canada concerning large static structures in Turbo C v2.0.
- I just recently had occasion to write a program that used a large static
- three-dimensional array of characters. When I calculated the number of bytes
- needed to hold the array, it came out to more than 150Kb. I tried to compile
- the program with the compact memory model and received the same error message
- as was mentioned, "Too much data...."
- Please see Listing 3 for my solution to this problem. I declared a
- two-dimensional array of huge pointers as global data. In the program I use
- the function farcalloc to add the third dimension to the array. After the
- memory data area has been allocated, all accesses to the data appear just like
- a three-dimensional array.
- I hope this helps.
- D. F. Spencer
- San Jose, CA
-
-
- Externs In Headers
-
-
- In reference to your column in the October 1990 issue (page 83), the following
- is an approach that I use to solve the question posed by Andreas Lang.
- #ifdef MAIN
-
- #define EX
- #define EQ(i) = i
-
- #else
-
- #define EX extern
- #define EQ(i)
-
- #endif
-
- EX int i EQ(5);
- This code excerpt allows a single header file to provide both the declarations
- and the external references.
- I always #undef EX and EQ at the end of the header, but this is not required.
- Gary Hoskins
- Castroville, CA
-
-
- Pointers
-
-
- Rex Jaeschke's illuminations about pointers and your recent column got me to
- do some experimenting on my own as follows:
- int p[10];
- int (*q)[10] = (int (*)[10])
- malloc( 2 * 10 * sizeof(int));
- I thought I could assign *q, q[0], or q[1] another address just like I can any
- other pointer. I understand that p is a constant pointer and can't be
- reassigned another address, but what should stop me from treating any single
- dereferencing of q as just another assignable address. After all, with my
- Turbo C compiler I can do q = &p, but why can't I do *q = p? What I got was a
- "need lvalue" error in compilation.
- I was mystified at first and tried a parallel-to-q creation of r.r and q were
- now of the same type. I tried to assign q[1] = r[1], bonnnkkkk!, but q = r and
- q[1][1] = r[1][1] worked fine. I concluded that the compiler must simply treat
- int [10] like it does p. So q[0] now references a declared array just like p
- and can't be assigned another value as far as my compiler is concerned.
-
- I hope my analysis of this is correct and I've not slipped a gear this
- evening. Thank you for the hearing.
- Mike Hanna
- San Francisco, CA
- You are right on. q is a pointer to objects that are int[10], that is, arrays
- of 10 integers. *q represents that array of integers. Note that some compilers
- will give a warning of "& operator on array name ignored" if you assign q =
- &p. All you need is q = p.
- To follow along with your example, suppose you had:
- int array_of_arrays[3][10] = {
- {1,2,3,4,5,6,7,8,9,10},
- {11,12,13,14,15,16,17,18,19,20},
- {21,22,23,24,25,26,27,28,29,30},
- };
- int (*q)[10];
- q = array_of_arrays;
- q contains the address of array_of_arrays. q[0] is the address of the first
- element in array_of_arrays, i.e., the value of array_of_arrays[0]. Since this
- happens to be an array, it is an address, which of course has the same value
- (but not the same type) as array_of_arrays. q[1] is the address of the second
- element in array_of_arrays, i.e., the value of array_of_arrays [1], and so
- forth.
- Let's put some numbers to this example. ints are two bytes and array_of_array
- is located at address 100. See Listing 4. (KP)
- [Confusion between pointers and arrays is endemic among even the brightest C
- programmers. They are two quite different creatures, but array names get
- converted to pointers so effortlessly and so often that they often appear
- interchangeable. When they are not, as in the case reported by Mr. Hanna, look
- out. -- PJP]
-
-
- Structure Access
-
-
- I'm writing in response to the letter from R. Palmer Benedict (The C Users
- Journal, November 1990, p. 104). I wanted to address some of the issues raised
- by that letter, which you didn't get into. First let me note that my
- investigations (see Listing 5) support Benedict's statement that accessing a
- structure member directly is the fastest method of access. It was Benedict's
- letter, by the way, that induced me to run timing tests, since I prefer to
- access data by means of arrays whenever possible.
- In re-reading the letter, I ran across some rather strange logic. No code
- accompanied the letter, so I may be misinterpreting Benedict's remarks.
- However, there seems to be some incongruity in his method. Benedict states,
- "Pointers or indices can access the data in the fields and subfields, which is
- much slower than accessing elements of a structure." Three different methods
- of access are represented in Benedict's statement. The fastest is direct
- access to a member of a structure variable via the dot operator:
- i = structure.member;
- The slower methods include access via a structure pointer,
- i = s_pointer->member;
- and access via a properly initialized array index:
- i = array[fieldnum];
- Perhaps Benedict's somewhat convoluted method of creating a structure variable
- (by using an array to set aside memory, and aiming a structure pointer at that
- memory) has led to some confusion on his part. He remarks that he can "access
- the elements of the structure through the structure pointer." Certainly he
- can, but by doing so he loses the speed advantage of direct access to a
- structure variable! He winds up defeating his own apparent purpose of using
- the fastest access method. As it turns out, he's using the slowest of the
- three methods of access (slowest on my machine, at least).
- I was disappointed to find I had to agree with Benedict that access via an
- array is slower than some other methods. I really prefer keeping the fields of
- a database record in an array as opposed to a structure, because I can access
- any field simply by changing an array index value. Or I can print all the
- fields in a concise loop containing only one print statement, while the loop
- increments the array index. (For relevant code examples see the function
- Verify-Contents() in Listing 5.) On the other hand, direct access to a
- structure variable requires that I specifically enter the ID of the structure
- member that I want. To print them all, I have to request each one
- individually. The same is true for access via the structure pointer. I was
- relieved to observe that access via an array is faster than access by
- structure pointer.
- I couldn't understand why there would be any speed variation involved in this.
- It seemed to me that the same thing was happening in each case. The parts of a
- structure are accessed by means of offsets, which is what array indexing is
- all about. So when I found that there was a difference in speed, I decided to
- delve a little deeper. I switched to the tiny memory model, and told the
- compiler to generate a map file. By using the tiny model, I could ignore
- memory segments and find locations in the .EXE file by offsets alone. Then I
- used DEBUG to view the code. It turns out that the speed of the routine
- depends heavily upon when the address calculation occurs -- at compile time or
- runtime. Machine constraints also affect performance.
- The fastest access that I came upon was the direct access to a member of a
- structure variable via the dot operator. Using the map file, I found the
- location of the UsingS_Object function in the .EXE file. Stripping away the
- loop code to leave just the access statements produced
- MOV WORD PTR [1E4A],1D7A
- MOV WORD PTR [1E4A],1DAC
- MOV WORD PTR [1E4A],1DDE
- MOV WORD PTR [1E4A],1E10
- In assembly language, the assignment is from right to left (as it is in BASIC
- and C). Each of the four hex constants 1D7A...lE10 in turn is assigned to the
- destination variable [1E4A]. This is the pointer variable named access in
- Listing 5. The four hex constants are hard-coded addresses of the structure
- members being accessed. There is in fact no indexing, no offset calculation at
- runtime. That's why this is the fastest method.
- The second fastest access that I measured was access via array. The following
- code is the machine representation of array access, extracted from the
- function UsingArray().
- MOV AX, [1E42]
- MOV [1E4A] ,AX
- ;
- MOV AX, [1E44]
- MOV [1E4A],AX
- ;
- MOV AX,[1E46]
- MOV [1E4A],AX
- ;
- MOV AX,[1E48]
- MOV [1E4A],AX
- Each of the four accesses requires two machine statements. The hex value
- [1E42] in brackets is array element 0. The value referenced by this pointer is
- moved to the machine register AX and from there to the same destination that
- was used in the faster code above. The machine cannot perform transfers from
- one memory location to another, without the intermediate step of storage in a
- register. If I had declared access to be a register variable, this routine
- might have been as fast as the previous method.
- Since each access by array requires two steps, it isn't quite as fast as the
- direct access approach. Yet it is faster than accessing structure members via
- a structure pointer:
- MOV AX,[1786]
- MOV [1E4A],AX
- ;
- MOV AX,[1786]
- ADD AX, 0032
- MOV [1E4A],AX
- ;
- MOV AX,[11786]
- ADD AX, 0064
-
- MOV [1E4A],AX
- ;
- MOV AX,[1786]
-
- ADD AX, 0096
- MOV [1E4A],AX
- These lines are from the Using-Structure() function. Here, an offset is added
- to the contents of the structure pointer at runtime to locate the desired
- member. Now three steps are required for each access (except the first). That
- slows this routine down a bit more than the access via array.
- It would be interesting to see how the offsetof macro translates into machine
- code. I suspect that the code would look like the final example above.
- offsetof would be figured at compile time, to calculate the values which in
- the above example are the constants that get added to the AX register at
- run-time.
- It would also be interesting to see how close Mr. Benedict is to the target
- with another remark: To "read each field or subfield individually into its
- place in the structure... can be dreadfully slow." I wonder how much slower it
- is to assign one field at a time, rather than all at once. We're not talking
- interpreted BASIC here. In C, using the <stdio.h> FILE functions, a disk
- access fills a buffer in RAM. Then individual fields and subfields would be
- read from this buffer. Shouldn't be all that slow...
- I'm not sure that the search for the fastest algorithm is as important as it
- used to be. There have been great advances in hardware speed over the last
- several years. The fine-tuning speed adjustments that software allows are
- small by comparison. They can be sacrificed in a tradeoff. This permits
- selection of algorithms and writing of code that is easier to read and
- maintain. One can let the hardware take care of the speed factor. In just the
- same way, high-level languages now do what used to be done in assembley
- language. The more competent machines can handle these larger, less efficient
- programs with ease. Ultimate machine code efficiency is no longer a primary
- consideration.
- Some comments on Listing 5:
- As written, a routine is iterated 70,000 times, and then the elapsed time is
- measured. The routines should probably be set up to run for a given period of
- time while the repetitions are counted. At the very least, they should be
- clocked by a timer with better resolution than the BIOS clock. Some of these
- functions perform 70,000 iterations of a test in about 18 clock ticks, which
- is one second. That's almost 4,000 iterations per tick. If one routine is on
- the quick side of 17 ticks, and another is on the slow side of 18 ticks, the
- difference could almost be 8,000 iterations, or well over 10 percent. That's a
- wide error margin. Nonetheless, I'm using the BIOS clock for a quick benchmark
- timer.
- One of the test functions in Listing 5 is an empty loop. This is included as a
- sort of test of my benchmark routine logic. The analyze routine compares the
- time to execute two separate loops against the time to execute a single loop
- which does the work of the other two. The time difference between these should
- be close to the time to execute an empty loop that does no work. It is.
- The .EXE file analyzed above was produced by TurboC v1.5 and compiled (for
- this analysis) in the tiny memory model. The benchmark times in Listing 6 were
- produced on a 10 MHz AT clone.
- Art Shipman
- Westbrookville, NY 12785
- Thanks for your tests and comments. I agree with your comment that ultimate
- machine efficiency is not a primary consideration these days -- unless the
- time required by inefficient code makes the user go out for coffee.
- If I wanted to access the individual fields in the structures by using a loop,
- I would declare
- field_addresses[] = {
- &S_object. field1,
- &S_object.field2,
- &S_object.field3,
- &S_object.field4};
- and then use a loop as
- for (i = 0; i < 4; i++)
- {
- printf("\n Field %d is %50.50s",
- field_addresses [i]);
- }
- That would probably be almost as fast as using an array access, since the
- offset addresses are precomputed. (KP)
-
-
- Casts And ANSI Standard
-
-
- Your column in the November 1990 issue included a letter from Hans-Gabriel
- Ridder, concerning the construct
- char *ptr;
- ((long *) ptr)++;
- His ANSI-conformant Lattice C compiler gave him an "lvalue required" error on
- this. Though he had used constructs like this for years, Lattice told him that
- "casts not being lvalues: was required by ANSI, in the name of portability."
- As far as I can tell, a cast has never yielded an lvalue, even in "classic"
- K&R C. And ++ has always required an lvalue. I'm not surprised that many early
- compilers would accept such a construct without complaint and even give the
- expected results. I think early compilers often accepted syntax that was not
- really legal.
- I'm also pretty sure that some pre-ANSI compilers would (correctly) reject
- such an expression. Allowing casts (even to pointer values) to be lvalues
- would permit such strange things as
- (int *)f() = g();
- when what was probably intended was
- *(int *)f() = g();
- Mr. Ridder was especially concerned about the case where the pointer is
- dereferenced along with the post-increment, in a manner similar to
- char *cptr;
- f(*cptr++);
- but treating cptr as a pointer to long. You suggested using
- *(cptr += sizeof(long))
- instead of
- *(char *) (((long *) ptr)++)
- but this does not have the same effect, since cptr would be incremented before
- dereferencing, and I think Mr. Ridder wanted the dereferenced value to be a
- long instead of a char anyway. (If not, the solution below won't help him).
- Your editor claimed parenthetically that "you can also get the old behavior by
- writing: ((long*)&ptr)++," but this is also clearly wrong. This still tries to
- increment the result of a cast.
- I think the editor was trying to say the following:
- *(*(long **)&cptr)++
- This will treat cptr as a pointer to long, dereference it, and post-increment
- it. This is legal C, both classic and ANSI, but it is not guaranteed to work.
- It assumes that you represent and manipulate pointers to char and pointers to
- long in the same way, and that there is no problem with the alignment of the
- objects being pointed to. You can think of it as a tricky way of getting the
- effect of a union without actually using one.
- As a test, I tried the following functions on Zortech C 2.12:
- char *g1(char *cptr)
- {
-
- f(*(*(long **)&cptr)++);
- return cptr;
- }
-
- long *g2(long *cptr)
- {
- f(*cptr++);
- return cptr;
- }
- With no optimization, g1 generated shorter code than g2! With optimization,
- the code generated was identical for these functions.
- Raymond Gardner
- Englewood, CO
- Thanks for your tests. As you say, mixing pointers to ints and longs or any
- other data objects for that matter is a tricky business. I prefer more
- straightforward methods, as the unions you mentioned.
- When I see expressions like
- *(*(long **)&cptr)++
- it reminds me why some novices wonder how they ever got into this language. I
- prefer breaking a tricky expression into two parts, with the increment being
- put before or after the use of cptr, as desired.
- *cptr = 'a';
- cptr += sizeof(long))
- Okay, it'll take a microsecond or so more. It took me a few seconds to figure
- out that *(*(long *)&cptr)++ = 'a' actually transfers four bytes (size of a
- long), even though cptr is a pointer to char. That's a tradeoff of more than a
- million to one. Of course, if you really wanted to transfer just a character,
- it would be * (char *) (*(long *)&cptr)++ = 'a'. Say that fast 10 times. (KP)
-
-
- Answer to Halloween/Christmas
-
-
- Because OCT 31 equals DEC 25. (Octal 31 equals decimal 25.)
-
- Listing 1
- #define BEGIN {
- #define END }
-
- main()
- BEGIN
- ....
-
- for ()
- BEGIN
- ....
- END
- END
-
-
- Listing 2
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
-
- int comp(const void *, const void *);
- unsigned char *list[] = { "cat", "car", "cab",
- "cap", "can" };
-
- int main()
- {
- int x;
-
- qsort(list, 5, sizeof(unsigned char *), comp);
- for (x = 0; x < 5; x++)
- printf("%s\n", list[x]);
- return 0;
- }
-
-
- int comp(const void *a, const void *b)
- {
- unsigned **A = (unsigned char **)a;
- unsigned **B = (unsigned char **)b;
- return strcmp(*A, *B);
- }
-
-
- Listing 3
- /* memtest.c --- test bed program for checking some
- huge arrays */
- /* ...use COMPACT memory model */
-
- #include <stdio.h>
- #include <alloc.h>
-
- char (huge *pinno)[20][8]; /* each term can have up
- to 20 pins */
-
- void main(void)
- {
- int i, j, k;
-
- if ( (pinno=farcalloc(sizeof(*pinno),1000)) ==NULL)
- {
- printf("\nallocation error");
- printf("...insufficient memory available...\n");
- exit(1);
- }
-
- printf("\narray of 160000 characters starts at %p \
- (norm)\n",pinno);
- printf("\narray at %Fp (far)\n",pinno);
-
- for (i = 0; i < 1000; i++)
- {
- for (j = 0; j < 20; j++)
- {
- for (k = 0; k < 7; k++)
- pinno[i][j][k]=0x41+k+j;
- pinno[i][j][k]='\0';
- }
- }
-
- for (i = 0; i < 1000; i++)
- {
- for (j = 0; j < 20; j++)
- printf("\narray element [%d][%d] = %s",i,j,
- pinno[i][j]);
- }
- }
-
-
- Listing 4
- Expression Value Type
-
- array_of_arrays 100 (int *)[10]
- array_of_arrays[0] 100 int *
-
- array_of_arrays[0][0] 1 int
- array_of_arrays[1] 120 int *
- array_of_arrays + 1 120 (int *)[10]
- array_of_arrays[0] + 1 102 int *
- q 100 (int *)[10]
- q[0] 100 int *
- q[0][0] 1 int
- q[1] 120 int *
- q + 1 120 (int *)[10]
- q[0] + 1 102 int *
-
-
- Listing 5
- #include <stdio.h>
- #include <bios.h>
- #include <string.h>
-
- #define LIMIT 70000L
-
- #define INIT \
- long i; \
- long t = biostime(0,0)
-
- #define TEST(x) \
- INIT; \
- for(i=0; i<LIMIT; i++){ \
- x; \
- } \
- return (int)(biostime(0,0)-t)
-
- #define Access(x,y) \
- printf("Accessing %s(): result = %d\n", #x,
- elapsed[y]=x() )
-
- typedef struct anything
- {
- char field1[50],
- field2[50],
- field3[50],
- field4[50];
- } anything;
-
- anything s_object, *s_pointer=&s_object;
-
- char *DestArray[4];
- char *SourceArray[4];
- char *access;
- int j=0;
-
- enum list { STRUCTURE, ARRAY, BOTH, EMPTY, ONELOOP,
- TWOLOOPS, S_OBJECT };
-
- int UsingS_Object(void)
- {
- TEST( access = (char *)&s_object.field1;
- access = (char *)&s_object.field2;
- access = (char *)&s_object.field3;
- access = (char *)&s_object.field4;);
- }
-
-
- int UsingStructure(void)
- {
- TEST( access = s_pointer->field1;
- access = s_pointer->field2;
- access = s_pointer->field3;
- access = s_pointer->field4; );
-
- int UsingArray(void)
- {
- TEST( access = SourceArray[0];
- access = SourceArray[1];
- access = SourceArray[2];
- access = SourceArray[3]; );
- }
-
- zint UsingStrucAndArrayBoth(void)
- {
- TEST( access = s_pointer->field1;
- access = s_pointer->field2;
- access = s_pointer->field3;
- access = s_pointer->field4;
-
- access = SourceArray[0];
- access = SourceArray[1];
- access = SourceArray[2];
- access = SourceArray[3]; );
- }
-
- int EmptyLoop(void)
- {
- TEST( ; );
- }
-
- int LoopToLoop(void)
- {
- TEST( for(j=0; j<4; j++){ DestArray[j] =
- SourceArray[j]; } );
- }
-
- int OneLoop(void)
- {
- TEST( for(j=0; j<4; j++){ access = SourceArray[j]; } );
- }
-
- void analyze(int *times)
- {
- int total = times[STRUCTURE] + times[ARRAY];
- int difference = total - times[BOTH];
-
- printf("\n1. Sum of time for (UsingStructure + \
- UsingArray) = %d\n", total );
-
- printf("2. Sum less time for both in one loop = %d\n",
- difference );
- printf("3. Time to do an empty loop = %d\n",
- times[EMPTY] );
- printf("4. Error (item 3 less item 2 should be near \
- zero) = %d\n",
-
- times[EMPTY]-difference );
- }
-
- void CompareAccessTimes(void)
- {
- int elapsed[10];
- Access(UsingS_Object,S_OBJECT);
- Access(UsingStructure,STRUCTURE);
- Access(UsingArray, ARRAY );
- Access(UsingStrucAndArrayBoth, BOTH);
- Access(EmptyLoop, EMPTY);
- Access(LoopToLoop,TWOLOOPS);
- Access(OneLoop,ONELOOP);
-
- analyze(elapsed);
- }
-
- void VerifyContents(void)
- {
- int i;
- puts("The data to be accessed is as follows:\n");
-
- /*
- printf("The structure contains the following...\n");
- printf("\t%s\n\t%s\n\t%s\n\t%s\n",
- s_pointer->field1,
- s_pointer->field2,
- s_pointer->field3,
- s_pointer->field4);
- */
- printf("\nThe array provides access to...\n");
- for(i=0; i<4; i++)
- {
- printf("\t%s\n", SourceArray[i] );
- }
- puts("\nEach function returns the count of clock ticks."\
- "The lower the better.\n);
- }
-
- void main(void)
- {
- printf("In this example, the size of a data pointer is %d,",
- sizeof(char*));
- printf("and the size\nof a function pointer is %d\n",
- sizeof(main) );
-
- /* initialize the structure */
-
- strcpy(s_pointer->field1,"This is field one");
- strcpy(s_pointer->field2,"Here's the second field");
- strcpy(s_pointer->field3,"Field Three at your service");
- strcpy(s_pointer->field4,"Art was here");
-
- /* initialize the purportedly slower array */
-
- SourceArray[0] = s_pointer->field1;
- SourceArray[0] = s_pointer->field2;
- SourceArray[0] = s_pointer->field3;
- SourceArray[0] = s_pointer->field4;
-
-
- VerifyContents();
-
- CompareAccessTimes();
- }
-
-
- Listing 6 Output from Listing 5
- In this example, the size of a data pointer is 2, and the size
- of a function pointer is 2
- The data to be accessed is as follows:
-
- The array provides access to...
- This is field one
- Here's the second field
- Field Three at your service
- Art was here
-
- Each function returns the count of clock ticks. The lower the better.
-
- Accessing UsingS_Object(): result = 14
- Accessing UsingStructure(): result = 18
- Accessing UsingArray(): result = 16
- Accessing UsingStrucAndArrayBoth()(: result = 25
- Accessing EmptyLoop(): result = 8
- Accessing LoopToLoop(): result = 57
- Accessing OneLoop(): result = 48
-
- 1. Sum of time for (UsingStructure + UsingArray) = 34
- 2. Sum less time for both in one loop = 9
- 3. Time to do an empty loop = 8
- 4. Error (item 3 less item 2 should be near zero) = -1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Stepping Up To C++
-
-
- Writing Your First Class
-
-
-
-
- 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 TECH
- Specialist. 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.
-
-
- Over the past two years I've spoken with many C programmers who are thinking
- about switching to C++. Nearly all have heard about the highly touted benefits
- of object-oriented programming (OOP) and C++. Although a few are skeptical
- about the benefits of C++, most programmers are intrigued and want to know
- more about it.
- As I mentioned in my last column, I think some apprehension is justified. C++
- is not as widely available as C. Where C++ is available, the development tools
- are sometimes lacking. And there's no formal standard for C++. Many C
- programmers are just starting to appreciate programming in a mature,
- standardized language. Understandably, they are reluctant to switch to C++.
- Most of all, I think C programmers are confused, intimidated, or just put off
- by the overly zealous preaching of a few highly vocal "true believers" in OOP.
- These zealots are hard to avoid -- every software development organization
- seems to have at least one. The zealots want you to believe that you shouldn't
- use C++ for anything other than OOP (using both inheritance and polymorphism).
- I've heard C programmers express their frustration with learning C++ under the
- shadow of a zealot. It's rather disconcerting to have your self-improvement
- efforts belittled by someone who thinks you haven't improved enough. You
- expect to hear, "Keep up the good work!" Instead you hear, "OK, but you really
- could have done it better." (Some of my former university students will
- probably be amused to read this, wondering, "When did this guy mellow out?"
- Age works wonders on us all.)
- The fact is, to a C programmer, C++ has many new features. Don't expect to
- grasp them all at once. Many of these features are intended to solve problems
- that arise in large-scale software systems. Small programming examples such as
- printing Hello, world, rarely make a convincing case for the advantages of C++
- over C. To really learn C++, you need to work through large examples that take
- time to develop.
- Complex programming problems usually have more than one solution. You should
- try different approaches to see which one works best for each situation. As
- Bjarne Stroustrup wrote in his first book on C++, "To write good programs
- takes intelligence, taste, and patience. You are not going to get it right the
- first time; experiment!" [1]
- Experimenting is fine if you're just writing practice programs, but most
- programmers must work for a living. They don't have much spare time to
- experiment with different design and programming techniques. Programmers and
- project managers must continue to try new tools and techniques to improve
- quality and productivity, but too much innovation all at once is risky.
- If you use object-oriented techniques in your first large C++ program, you
- might develop some reusable components that dramatically reduce the program's
- code size. More likely, you'll make lots of mistakes. If you believe, as Fred
- Brooks suggests [2], that you should "plan to throw one away," then maybe you
- can plunge into C++ and OOP all at once. If you're not prepared to throw the
- program away, you're courting disaster.
-
-
- Learning C++ In Stages
-
-
- As an alternative, I think it's not only possible but preferable, to learn and
- apply C++ in stages. You can't learn to use virtual functions (polymorphism)
- unless you understand inheritance, and inheritance only makes sense if you
- understand classes (encapsulation). The best way to learn encapsulation is to
- apply it in real programs, and you can write lots of useful C++ programs using
- encapsulation without inheritance. Languages that provide encapsulation
- without inheritance are called object-based [3], whereas languages that
- support inheritance are called object-oriented.
- Most people learn to program by reliving the evolution of programming
- languages. New languages appear as programmers run up against the limitations
- of existing languages. Object-based languages, like Ada and Modula-2,
- represent a major evolutionary step between data-structured languages, such as
- C and Pascal, and object-oriented languages such as C++. Just as the languages
- took years to evolve, programmers need time (months, if not years) to progress
- through these evolutionary stages. Everyone progresses at a different rate.
- If you've ever tried explaining the virtues of pointers and user-defined data
- types to a FORTRAN programmer, then you probably know what I'm talking about.
- Most FORTRAN programmers who crunch numbers for a living can't understand why
- arrays aren't adequate for structuring any collection of data you could ever
- want. I've had similar difficulty explaining information hiding
- (encapsulation) to students who have never maintained someone else's code. If
- you haven't experienced the problem, you can't appreciate the solution.
- Many C programmers want to start using C++, but they're reluctant to take the
- OOP plunge. OOP is not the only reservation programmers have about C++, but it
- is significant. C++ is, for all practical purposes, a superset of C. C++ was
- designed to be integrated with existing C code and practice. The language
- permits you -- but doesn't force you -- to overhaul your design and
- programming styles. You can gain a lot by using C++, even if you only apply it
- a little bit at a time.
- If you want to start small with C++, then use it as an object-based language.
- Find some part of your application that could be a separate, well-defined
- entity, and implement that entity as a class. Try writing the class so that
- its public interface hides the implementation details from the rest of the
- application. The following example does this by analyzing the weaknesses of an
- existing C program.
-
-
- A Cross-Reference Generator
-
-
- A cross-reference generator program reads a document and prints an
- alphabetized list of the words appearing in that document. Each word in the
- output listing is followed by a sequence of line numbers on which that word
- appears in the document. For example, if the word object appears once on lines
- 3, 19, and 100, and twice on line 81, then the cross-reference listing entry
- for object is
- object 3 19 81 100
- This program is suggested as exercise 6-3 in K & R [4].
- Listing 1 shows a portion of xr.c, an implementation of the cross-reference
- generator. xr is based on the solution presented by Tondo and Gimpel [5]. I
- modified their solution to compile in C++ as well as C, and made a few small
- stylistic changes.
- xr works as follows: Each call to getword reads the next word, punctuation
- character, or newline character from the input document. If getword returns a
- word (a sequence of letters and digits starting with a letter), the program
- adds an entry to the cross-reference table containing that word and the
- current line number. If getword returns a newline, the programs increments the
- current line number. After reading the entire input, the program prints the
- cross-reference listing.
-
-
- Hiding The Details
-
-
- Listing 1 illustrates a problem that plagues most large programs. The program
- contains declarations at the main level that reveal design and implementation
- decisions made at lower levels. These declarations reduce the program's
- readability and maintainability by cluttering the main level with
- inappropriate detail. This clutter isn't much trouble in small programs but
- can be overwhelming in programs with tens or hundreds of declarations.
- For example, it's evident from Listing 1 that xr implements the
- cross-reference table as a binary tree. Why? For starters, the function that
- adds an entry to the cross-reference is called addtree, and the output
- function is called printtree. Both functions accept root as an argument, and
- root is of type treenode *.
- If you refer to my description of how xr works, you'll see that it never
- mentions trees. At this level in the program design, you don't need to know
- how the table is implemented, but you should know what the table does
- A good implementation of the cross-reference program keeps these concerns
- separate. Each design decision, like the structure of the cross-reference
- table, should be hidden in a separate part of the program. This practice of
- isolating design decisions is known as information hiding or data hiding. In
- the OOP world, it's called encapsulation.
- C provides only limited support for encapsulation. You hide information by
- placing code in separately compiled modules. Understanding the technique and
- its limitations will help you appreciate C++ classes, so I'll demonstrate the
- technique by applying it in the implementation of xr.
-
-
- Encapsulation With C
-
-
-
- Listing 2 through 4 present a better implementation of xr using some
- encapsulation by separate compilation. xr.c (Listing 2) is the main source
- file. The implementation of the cross-reference table is hidden in xrt.c
- (Listing 3). The header xrt.h (Listing 4) defines the interface to xrt.c. That
- is, the header declares the functions through which the main program accesses
- the hidden table.
- Notice that all evidence that the cross-reference table is a binary tree is
- gone from the xr.c. The variable root, the functions addtree and printtree,
- and the struct definitions have all been moved from xr.c to xrt.c. root,
- addtree, and printtree are declared static in xrt.c, so they can't be
- referenced directly by code in xr.c.
- The functions xrt_add and xrt_print, declared in xrt.h and defined in xrt.c,
- provide the only access from xr.c to cross-reference table data structure.
- Instead of calling addtree directly, main must call xrt_add. xrt_add passes
- root to a call to addtree but keeps both root and addtree hidden inside xrt.c.
- Similarly, main must call xrt_print to invoke printtree.
- The key to this encapsulation technique is the selective use of the static
- storage specifier. You place the data to be hidden, along with the functions
- that manipulate that data, in a single, separate source file. You declare all
- the data at file scope, and almost all of the functions static. The only
- functions that should be extern are those few that grant access to the data
- structure from the outside world. The access function names and prototypes
- should clearly indicate what they do, but give little or no clue as to how
- they work.
- This implementation of xr is more readable than the first one. The input
- processing is clearly distinct from the table processing. main more clearly
- describes what it does without unnecessary and intrusive details about the
- inner workings of the table. This program is also more maintainable. The
- structure of the cross-reference table is so well hidden that you can change
- it to a b-tree or hash table without even recompiling the main source file.
-
-
- Where C Breaks Down
-
-
- Ideally, each encapsulation unit hides a single design decision. However, the
- table implementation in xrt.c (Listing 3) actually embodies two decisions:
- the table is a binary tree
- each sequence of line numbers is a singly-linked list referenced by a single
- pointer in each tree node
- Just as the implementation of the table should be hidden from main, the
- implementation of each line number sequence should be hidden from the table
- module.
- Unfortunately, the separate compilation technique that seems to work so well
- at hiding the table is completely inadequate for hiding the implementation of
- the line number sequences. The problem is that there's exactly one sequence
- for each tree node. The representation of a sequence must be declared as part
- of struct treenode. You can't store the sequences in static data in another
- module.
- So what do you do? Implement line number sequences as a C++ class.
-
-
- Encapsulating With C++
-
-
- The header file ln_seq. h (Listing 5) contains the declaration for a class of
- line number sequences called ln_seq. ln_seq provides a constructor and two
- public access functions: add and print. The class has one private data member,
- first, which points to the first element in the linked list implementation of
- the sequence.
- Note that the type of each linked list node, listnode, is a nested type. That
- is, listnode is declared inside class ln_seq. Versions of C++ compatible with
- AT&T C++ 2.0 (or earlier) treat nested classes as if they were declared in the
- scope of the enclosing class, but AT&T C++ 2.1 (and the current draft standard
- for C++) treat nested classes as local to the enclosing class. My intention in
- Listing 5 is that listnode should be private within ln_seq. Although many
- compilers don't yet enforce this access restriction, they will eventually.
- ln_seq. cpp (Listing 6) implements the member functions of the class. The
- constructor ln_seq::ln_seq is trivial -- it just initializes the list to null.
- The body of ln_seq::print is simply the for loop that appeared in xrt_print.
- ln_seq::add is the addnumber function from Listing 3, with one noteworthy
- change.
- ln_seq::add contains additional code to handle the case where the list is
- empty, i.e., first is null. addnumber never confronts this case because
- addtree (Listing 3) creates each list with an initial node. I could have
- written the constructor to initialize the sequence with an initial line
- number, but handling the null in the add function is easier.
- Listing 7 shows xrt.cpp, the cross-reference table handler rewritten using the
- line number sequence class. It now shows no hint of how the sequences are
- implemented. You can safely change the implementation of ln_seq without
- changing xrt. cpp (although you will probably need to recompile).
- For example, if you use only a single pointer to the head of each list, then
- every time you add an element, you have to search through the entire list to
- find the end. If you track the end of each list in a second pointer, you
- eliminate the searching. Listing 8 and Listing 9 show this alternative
- implementation.
- An added benefit of using classes is that it forces you to think carefully
- about the interfaces between components of your program. For example, notice
- that the first parameter of addnumber (Listing 3) is a treenode, not a
- listnode. The function adds a line number to the sequence in a tree, rather
- than to a list by itself. The sloppiness of this design becomes apparent when
- you try to transform it into a member function of the line number sequence
- class.
-
-
- Looking Ahead
-
-
- xr is now an object-based program with lots of line number sequence objects.
- It uses only the most basic features of C++, but I think the C++ version of
- xrt (Listing 7) is better organized and more readable than the C version
- (Listing 3). More improvements could be made, but they will keep until some
- future column.
- You don't need a crash course in object-oriented design to write your first
- practical C++ class in a real-live application. Just identify a design
- decision and create a class to hide it. With practice, you'll get good at it.
- References
- [1] Stroustrup, Bjarne, The C++ Programming Language. Addison-Wesley, Reading,
- MA, 1986.
- [2] Brooks, Fred, The Mythical Man-Month. Addison-Wesley, Reading, MA, 1975.
- [3] Wegner, Peter, "Concepts and Paradigms of Object-Oriented Programming,"
- OOPS Messenger, Vol. 1, No. 1, Aug 1990.
- [4] Kernighan, Brian and Ritchie, Dennis, The C Programming Language, 2nd ed.
- Prentice-Hall, Englewood Cliffs, NJ, 1988.
- [5] Tondo, Clovis and Gimpel, Scott, The C Answer Book, 2nd ed. Prentice-Hall,
- Englewood Cliffs, NJ, 1989.
-
- Listing 1
- /*
- * xr.c - a cross-reference generator
- */
- #include <ctype.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
-
- typedef struct listnode listnode;
- struct listnode
- {
- unsigned number;
- listnode *next;
- };
-
- typedef struct treenode treenode;
-
- struct treenode
- {
- char *word;
- listnode *lines;
- treenode *left, *right;
- };
-
- treenode *addtree(treenode *t, char *w, unsigned n);
- void printtree(treenode *t);
- int getword(char *word, size_t lim);
-
- #define MAXWORD 100
-
- int main(void)
- {
- treenode *root = NULL;
- char word[MAXWORD];
- unsigned lineno = 1;
-
- while (getword(word, MAXWORD) != EOF)
- if (isalpha(word[0]))
- root = addtree(root, word, lineno);
- else if (word[0] == '\n')
- ++lineno;
- printtree(root);
- return 0;
- }
-
-
- Listing 2
- /*
- * xr.c - a cross-reference generator
- */
- #include <assert.h>
- #include <ctype.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
-
- #include "xrt.h"
-
- int getword(char *word, size_t lim)
- {
- int c;
- char *w = word;
-
- assert(lim > 2);
- while (isspace(c = fgetc(stdin)) && c != '\n')
- ;
- if (c != EOF)
- *w++ = c;
- if (!isalpha(c))
- {
- *w = '\0';
- return c;
- }
- for ( ; lim-- > 0; ++w)
- if (!isalnum(*w = fgetc(stdin)))
- {
-
- ungetc(*w, stdin);
- break;
- }
- *w = '\0';
- return *word;
- }
-
- #define MAXWORD 100
-
- int main(void)
- {
- char word[MAXWORD];
- unsigned lineno = 1;
-
- while (getword(word, MAXWORD) != EOF)
- if (isalpha(word[0]))
- xrt_add(word, lineno);
- else if (word[0] == '\n')
- ++lineno;
- xrt_print();
- return 0;
- }
-
-
- Listing 3
- /*
- * xrt.c - cross-reference table implementation
- */
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
-
- #include "xrt.h"
-
- typedef struct listnode listnode;
- struct listnode
- {
- unsigned number;
- listnode *next;
- };
-
- typedef struct treenode treenode;
- struct treenode
- {
- char *word;
- listnode *lines;
- treenode *left, *right;
- };
-
- static void addnumber(treenode *t, unsigned n)
- {
- listnode *p;
-
- p = t->lines;
- while (p->next != NULL && p->number != n)
- p = p->next;
- if (p->number != n)
- {
- p = p->next
-
- = (listnode *)malloc(sizeof(listnode));
- p->number = n;
- p->next = NULL;
- }
- }
-
- static treenode *addtree
- (treenode *t, char *w, unsigned n)
- {
- int cond;
-
- if (t == NULL)
- {
- t = (treenode *)malloc(sizeof(treenode));
- t->word =
- strcpy((char *)malloc(strlen(w) + 1), w);
- t->lines =
- (listnode *)malloc(sizeof(listnode));
- t->lines->number = n;
- t->lines->next = NULL;
- t->left = t->right = NULL;
- }
- else if ((cond = strcmp(w, t->word)) == 0)
- addnumber(t, n);
- else if (cord < 0)
- t->left = addtree(t->left, w, n);
- else
- t->right = addtree(t->right, w, n);
- return t;
- }
-
- static void printtree(treenode *t)
- {
- listnode *p;
-
- if (t != NULL)
- {
- printtree(t->left);
- printf("%12s: ", t->word);
- for (p = t->lines; p != NULL; p = p->next)
- printf("%4d ", p->number);
- printf("\n");
- printtree(t->right);
- }
- }
-
- static treenode *root = NULL;
-
- void xrt_add(char *w, unsigned n)
- {
- root = addtree(root, w, n);
- }
-
- void xrt_print(void)
- {
- printtree(root);
- }
-
-
-
- Listing 4
- /*
- * xrt.h - cross-reference table interface
- */
- void xrt_add(char *w, unsigned n);
- void xrt_print(void);
-
-
- Listing 5
- /*
- * ln_seq.h - line number sequence interface
- */
-
- class ln_seq
- {
- public:
- ln_seq();
- void add(unsigned n);
- void print();
- private:
- struct listnode
- {
- unsigned number;
- listnode *next;
- };
- listnode *first;
- };
-
-
- Listing 6
- /*
- * ln_seq.cpp - line number sequence implementation
- */
- #include <stdio.h>
-
- #include "ln_seq.h"
-
- ln_seq::ln_seq()
- {
- first =0;
- }
-
- void ln_seq::add(unsigned n)
- {
- listnode *p = first;
- if (first == 0)
- {
- first = new listnode;
- first->number = n;
- first->next = NULL;
- }
- else
- {
- while (p->next != 0 && p->number != n)
- p = p->next;
- if (p->number != n)
- {
- p = p->next = new listnode;
- p->number = n;
-
- p->next = 0;
- }
- }
- }
-
- void ln_seq::print()
- {
- listnode *p;
-
- for (p = first; p != 0; p = p->next)
- printf("%4d ", p->number);
- }
-
-
- Listing 7
- /*
- * xrt.cpp - cross-reference table implementation
- */
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
-
- #inctude "ln_seq.h"
- #include "xrt.h"
-
- struct treenode
- {
- char *word;
- ln_seq lines;
- treenode *left, *right;
- };
-
- static treenode *addtree
- (treenode *t, char *w, unsigned n)
- {
- int cond;
-
- if (t == NULL)
- {
- t = new treenode;
- t->word
- = strcpy((char *)malloc(strlen(w) + 1), w);
- t->lines.add(n);
- t->left = t->right = NULL;
- }
- else if ((cond = strcmp(w, t->word)) == 0)
- t->lines.add(n);
- else if (cond < 0)
- t->left = addtree(t->left, w, n);
- else
- t->right = addtree(t->right, w, n);
- return t;
- }
-
- static void printtree(treenode *t)
- {
- if (t != NULL)
- {
- printtree(t->left);
-
- printf("%12s: ", t->word);
- t->tines.print();
- printf("\n");
- printtree(t->right);
- }
- }
-
- static treenode *root = NULL;
-
- void xrt_add(char *w, unsigned n)
- {
- root = addtree(root, w, n);
- }
-
- void xrt_print(void)
- {
- printtree(root);
- }
-
-
- Listing 8
- /*
- * ln_seq.h - line number sequence interface
- */
-
- class ln_seq
- {
- public:
- ln_seq();
- void add(unsigned n);
- void print();
- private:
- struct listnode
- {
- unsigned number;
- listnode *next;
- };
- listnode *first, *last;
- };
-
-
- Listing 9
- /*
- * ln_seq.cpp - line number sequence implementation
- */
- #incLude <stdio.h>
-
- #incLude "ln_seq.h"
-
- ln_seq::ln_seq()
- {
- first = Last = 0;
- }
-
- void Ln_seq::add(unsigned n)
- {
- listnode *p;
- if (first == 0 last->number != n)
- {
-
- p = new listnode;
- p->number = n;
- p->next = NULL;
- if (first == 0)
- first = p;
- else
- last->next = p;
- last = p;
- }
- }
- void ln_seq::print()
- {
- listnode *p;
-
- for (p = first; p != 0; p = p->next)
- printf("%4d ", p->number);
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Implementer's Notebook
-
-
- Implementing A trap Command
-
-
-
-
- Don Libes
-
-
- Don Libes is a computer scientist at the National Institute of Standards and
- Technology. He is also the author of Life With UNIX, published by
- Prentice-Hall. His electronic mail address is libes@cme.nist.gov. He can also
- be reached at NIST, Bldg. 220, Rm A-127, Gaithersburg, MD 20899.
-
-
- Almost everyone has written programs for interpreters (such as UNIX shell
- scripts or DOS .BAT files). If you've ever tried to make them bulletproof,
- you've probably handled signals.
- The UNIX Bourne shell lets the user dictate actions using the trap command.
- Many other interpreters offer a similar feature. For example, the C-shell
- calls it onintr, and dBase calls it on error and on key. All of these commands
- work similarly.
- Even though they have different names, the idea is the same. In the event of a
- signal (error), you can get control and clean up before the interpreter exits.
- You might, for example, want to remove temporary files before exiting. In this
- column, I'll show how you can implement a trap-like command for your own
- interpreter.
- The trap command I'm going to create has the following syntax:
- trap [action] [list of signals]
- The action is a statement in the language of the interpreter. Early UNIX
- shells only accepted signal numbers, but modern shells (such as Gnu's bash)
- accept the actual signal macro names as defined in signal.h. This is easier on
- the user and more portable.
- As an example, if you write a bash script that has the line
- trap "echo ouch" SIGABRT
- and the interpreter calls abort (which raises the SIGABRT signal), the
- interpreter will print ouch.
-
-
- Implementing trap -- It's Obvious!
-
-
- To implement trap, you must simply call signal with the appropriate signal
- translated from its macro name. Unfortunately, the rest is not so simple. The
- second argument to signal is a pointer to a C function. The script programmer
- cannot easily describe the action, which must be coded in the language of the
- interpreter, as something like
- signal(SIGABRT, ????);
- You need to make the association between C code and an interpreter statement.
- So first assume that the interpreter statement is stored in a character array
- (called sigabrt_action here). Then call signal with a function, called
- sigabrt_handler, that evaluates the interpreter statement.
- void
- sigabrt_handler() {
- eval (sigabrt_action);
- }
- Here, eval evaluates the string containing the statement that the user
- originally associated with this signal via trap. That's the basic idea. You
- must still add a few things to sigabrt_handler. sigabrt_action must be set
- before passing it as an argument. If not, you must perform a default action.
-
-
- A Better Solution
-
-
- To finish this implementation, you must write an X_handler and X_action for
- each possible signal X, ending up with sigabrt_handler, sigfpe_handler,
- sigill_handler, and so on. While standard C defines only six signals, some
- implementations define dozens. Since the code for all signal handler is going
- to be similar, perhaps we can do better.
- We can. When a signal handler is executed, it is passed the signal number as
- its first argument. You can replace all of those distinct functions with a
- single generic signal handler:
- /* define NSIG to be the number of signals
- in your system */
- char *actions[NSIG];
- void
- sig_handler(sig) {
- eval (actions [sig] );
- }
- Listing 1 shows the complete implementation of the trap command.
- It is common and convenient to define the signal 0 to be raised upon program
- exit, even though the C standard doesn't define such a signal. [Nor does it
- promise that the value 0 belongs to no other signal. - pjp] You can write:
- trap "echo exiting" 0
- so that the script will write exiting as its last act, regardless of how it
- exits. (This is analogous to the onexit function in C.) Since actions [0]
- already exists, supporting signal 0 costs very little. The interpreter must
- simply call sig_handler (0) at termination.
- Listing 2 is a rewritten signal handler that does the other things I mentioned
- earlier. I've added a few useful features to sig_handler. The first is an
- assertion that verifies that the delivered signal is defined. This protects
- you against accessing outside the action array.
- Next, you must check if actions[sig] is defined. If actions[sig] isn't
- defined, sig_handler could not have even been called, except for the case
- where sig == 0. The 0 case occurs because sighandler(0) is always called at
- interpreter exit, whether or not a user action has been defined. Once
- verified, signal is called to reinstall the signal handler for systems that
- require this.
- Lastly, the handler executes a longjmp, just in case you have defined a valid
- environment in which to jump. For example, you might protect a call to func
- using the code in Listing 3. While the trap's signal handler forces setjmp to
- return 1 (thereby restarting func), other signal handlers could have setjmp
- return other values, allowing func to be aborted. As an aside, it is odd that
- the C standard does not allow longjmp to have setjmp return 0. There is no
- good reason for it! [As a further aside, yes there is. The program calling
- setjmp must be able to distinguish the intial call from a longjmp return. --
- pjp]
-
-
-
- The Final Version
-
-
- Now, I'll present the final version of the procedures to implement the trap
- command. In order to support a few features that I'll explain in a moment,
- signal information will be stored in the following array of structs, one per
- signal:
- struct {
- char *action;
- void (*defaultX) ();
- char *name;
- } signals[NSIG];
- action is the interpreter statement to execute when the signal arrives. If
- none has been defined, action equals 0 and defaultX, a C function, is
- executed. (I put the X at the end because default is a keyword!) name is the C
- macro defining the signal.
- Initializing this array is a little painful (Listing 4), but makes the rest of
- the code easy (see Listing 4). First, the signal names are initialized. The
- first six are defined by the C standard. The next one (signal 0) is my
- convention for onexit that I mentioned earlier. After this are several
- nonstandard (but common) signals. By using #ifdefs, the code is portable, yet
- supports extensions if they are there. Feel free to augment this list of
- signals. I've only provided a couple to give you the idea.
- The last part of init_trap saves the default actions. Since signal does a
- destructive read, you must call it twice, the second to rewrite the original
- value. If you want to provide a default action, you should register it with
- signal before ini_trap is run. Then, if the user ever sets the signal to
- SIG_DFL, it will be set to your default, not the system's.
- Listing 5 shows the final cmd_trap. I'll explain how it is to be used as I
- step through the code.
- First, notice that arguments are passed using the argc/argv convention. Even
- though this isn't a main routine, it's a simple way of handling differing
- arguments that are all of type char *. (Compare this to using stdargs.)
- If you provide no signals as arguments, trap just lists the signals for which
- it has actions defined by calling print_signal. See Listing 6. If you pass
- signals as arguments with no action supplied, trap lists the actions for just
- the named signals. signal_to_string is a trivial procedure that returns an
- appropriate printable string describing the given signal.
- The function string_to_signal converts signal names or numbers to the int form
- required by signal. In Listing 7, string_to_signal first tries to interpret
- the argument as a signal number, and then as a signal name. It even tries it
- without the prefix SIG. If the last element of argv isn't a signal, the
- program assumes it is an action. Fortunately, an action is not likely to look
- exactly like a signal.
- The remainder of cmd_trap loops through the signals, defining or printing them
- as appropriate. Any signal names that are prefaced by an asterisk are reserved
- to the interpreter and cannot be set by the user. As an example, in init_trap,
- I reserved SIGFPE this way.
- In order to set the signal, sig_trap discards the old action and saves the new
- action, allocating and freeing space as necessary. The reason an entirely new
- copy of the action is saved rather than just a pointer to it is that we must
- assume the interpreter can throw away strings after a command returns. For
- example, the action might have been stored in a variable that is overwritten
- after the call to trap.
- Lastly, the program calls signal. This system call recognizes two special
- actions. If the action is SIG_IGN, the signal is subsequently ignored. If the
- action is SIG_DFL, the signal subsequently gets the default treatment. In this
- case, the program sets the action to 0 so that signals with not appear in
- listings, just as when the program begins.
- sig_handler requires a few syntactic changes to support the new signal
- structure. Listing 8 shows the new version.
-
-
- Conclusion
-
-
- These routines provide a solid implementation of a complete and flexible
- signal-trapping command for a typical command interpreter. You could add a
- little more error checking. In particular, signal can return SIG_ERR upon
- failure. I'll leave you with the task of integrating this into the code, and
- thinking about when signal could possibly fail.
- Thanks to Steve Clark and Sarah Wallace for offering helpful suggestions on
- this column.
-
- Listing 1
- int
- cmd_trap(action, list,count)
- char *action; /* eval upon signal */
- int *list; /* list of sigs */
- int count; /* how many sigs */
- {
- int i;
-
- for (i=0;i<count;i++,list++) {
- actions[*list] = action;
- signal(*list,sig_handler);
- }
- }
-
- void
- init_trap()
- {
- int i;
-
- for (i=0;i<NSIG;i++) actions[i] = 0;
- }
-
-
- Listing 2
- jmp_buf env;
- int valid_env = 0;
-
- void sig_handler(sig)
-
- int sig;
- {
- assert(sig >= 0 && sig < NSIG);
-
- if(!actions[sig]) {
- /* always an error except when sig == 0 */
- if (sig == 0) return;
- fprintf(stderr,"unexpected signal (%d) delivered\n", sig);
- } else {
- signal(sig, sig_handler);
- eval(actions[sig]);
- }
-
- if(valid_env) longjmp(env, 1);
- }
-
-
- Listing 3
- if (1 >= setjmp(env)) {
- valid_env = TRUE;
- func();
- }
- valid_env = FALSE;
-
-
- Listing 4
- void
- init_trap()
- {
- int i;
-
- for (i=0;i<NSIG;i++) {
- signals[i].name = 0;
- }
-
- /* defined by C standard */
- signals[SIGABRT].name = "SIGABRTI";
- signals[SIGFPE ].name = "*SIGFPE";
- /* "*" means reserved to us - see below */
- signals[SIGILL ].name = "SIGILL";
- signals[SIGINT ].name = "SIGINT";
- signals[SIGSEGV].name = "SIGSEGV";
- signals[SIGTERM].name = "SIGTERM";
-
- /* our own extension */
- signals[0].name = "ONEXIT";
-
- /* nonstandard but common */
- #if defined(SIGHUP) /* hangup */
- signals[SIGHUP ].name = "SIGHUP";
- #endif
-
- #if defined(SIGALRM) /* alarm clock */
- signals[SIGALRM].name = "SIGALRM";
- #endif
-
- #if defined(SIGPWR) /* imminent power failure */
- signals[SIGPWR ].name = "SIGPWR";
- #endif
-
-
- #if defined(SIGIO) /* input/output signal */
- signals[SIGIO ].name = "SIGIO";
- #endif
-
- for (i=0;i<NSIG;i++) {
- signals[i].action = 0;
- signals[i].defaultX = signal(i,SIG_DFL);
- signal(i,signals[i].defaultX);
- }
- }
-
-
- Listing 5
- #include <stdio.h>
- #include <signal.h>
- #include <assert.h>
- #include <setjmp.h>
- #include <string.h>
- #include <stdlib.h>
-
- #define streq(x,y) (0 == strcmp(x,y))
-
- /* reserved to us if name begins with asterisk */
- #define SIG_RESERVED(x) (signals[x].name[0] == '*')
-
- void sig_handler();
- void print_signal();
-
- enum cmd_status {CMD_OK, CMD_ERROR};
-
- enum cmd_status
- cmd_trap(argc,argv)
- int argc;
- char **argv;
- {
-
- enum cmd_status rc = CMD_OK;
- char *action = 0;
- int len; /* length of action */
- int i;
-
- if (argc == 1) {
- for (i=0;i<NSIG;i++) print_signal(i);
- return(rc);
- }
-
- if (-1 == string_to_signaL(argv[argc-1])) {
- action = argv[argc-1];
- argc--;
- }
-
- for (i=1;i<argc;i++) {
- int sig = string_to_signal(argv[i]);
- if (sig < 0 sig >= NSIG) {
- fprintf(stderr,"trap: invalid signal %s",argv[i]);
- rc = CMD_ERROR;
- break;
- }
-
-
- if(!action) {
- print_signal(sig);
- continue;
- }
-
- if (SIG_RESERVED(sig)) {
- fprintf(stderr,"trap: cannot trap (%s)",argv[i]);
- rc = CMD_ERROR;
- break;
- }
-
- if (signals[sig].action) free(signals[sig].action);
-
- if (streq(action,"SIG_DFL")) {
- signal(sig,signals[sig].defaultX);
- signals[sig].action = 0;
- } else {
- len = 1 + strlen(action);
- if (0 == (signals[sig].action = malloc(len))) {
- fprintf(stderr,"trap: malloc failed");
- signal(sig,signals[sig].defaultX);
- rc = CMD_ERROR;
- break;
-
- }
- memcpy(signals[sig].action,action, len);
- if (streq(action,"SIG_IGN")) {
- signal(sig,SIG_IGN);
- } else signal(sig,sig_handler);
- }
- }
- return(rc);
- }
-
-
- Listing 6
- char *
- signal_to_string(sig)
- int sig;
- {
- if (sig < 0 sig > NSIG) {
- return("SIGNAL OUT OF RANGE");
- } else if (!signals[sig].name) {
- return("SIGNAL UNKNOWN");
- } else return(signals[sig].name + SIG_RESERVED(sig));
- }
-
- void
- print_signal(sig)
- int sig;
- {
- if (signals[sig].action) printf("%s (%d): %s\n",
- signal_to_string(sig),sig,signals[sig].action);
- }
-
-
- Listing 7
- /* given signal index or name as string, */
-
- /* returns signal index or -1 if bad arg */
- string_to_signal(s)
- char *s;
- {
- int sig;
- char *name;
-
- /* try interpreting as an integer */
- if (1 == sscanf(s,"%d",&sig)) return(sig);
-
- /* try interpreting as a string */
- for (sig=0;sig<NSIG;sig++) {
- name = signals[sig].name;
- if (SIG_RESERVED(sig)) name++;
- if (streq(s,name) streq(s,name+3))
- return(sig);
- }
- return(-1);
- }
-
-
- Listing 8
- jmp_buf env;
- int valid_env = 0;
-
- void
- sig_handler(sig)
- int sig;
- {
-
- assert(sig >= 0 && sig < NSIG);
-
- if (!signals[sig].action) {
- /* always an error except when sig == 0 */
- if (sig == 0) return;
- fprintf(stderr,"unexpected signal delivered - %s (%d)\n",
- signal_to_string(sig),sig);
- } else {
- signal(sig, sig_handler);
- eval(signals[sig].action);
- }
-
- if (valid_env) longjmp(env, 1);
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- On The Networks
-
-
- New Moderator Needed
-
-
-
-
- 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).
-
-
- Workload has hit another of the source group moderators on USENET network
- news. Brandon Allbery declared that he is too busy to moderate
- comp.sources.misc and asked in early October for volunteers to do the job.
- Thus for the interim, comp.sources.misc has been very quiet.
- The new moderator should be from a site directly connected to the Internet, so
- that he can update the archive sites easily. An efficient moderator needs
- about two to four hours per week just to maintain things. (I think Brandon
- underestimated the time necessary since it really takes more time to do a good
- job.) Some of Brandon's last postings are highlighted here.
- For those using the MS-DOS shell posted by Ian Stewartson
- <iarwqer@datlog.co.uk>, a major patch was issued as Volume 14, Issues 65 and
- 66, bringing the shell to version 1.6.3. Many bugs were fixed and support for
- POSIX P1003.2 commands has been started. POSIX is the standards body for an
- operating system that looks somewhat like UNIX. P1003.2 specifies the commands
- such an operating system should have, such as listing directories and copying
- files.
- The mail and news pretty-printer by Rich Burridge <rburridge@sun.com> has been
- enhanced and reposted. This new version, mp-2.4.5, will print mail or news
- messages using PostScript in a "pretty" format. It supports landscape mode and
- will print multiple pages in a reduced size on a single sheet of paper. It
- also can print normal ASCII files as well as a personal organizer format. For
- those with time to spare, a TODO list is included. mp-2.4.5 is Volume 14,
- Issues 67 and 68.
- C++ is filtering through the nets. S. Manoharan <sam@lfcs.edinburgh.ac.uk> has
- contributed a C++ class set for building event-based discrete event
- simulations (Volume 14, Issue 69). Silo provides classes for Entity, Event,
- Resource and Bin, and is based on M. H. MacDougall's book Simulating Computer
- Systems: Techniques and Tools.
- Wayne Davison <davison@dri.com> has invented a new context difference listing
- format that produces output roughly 25 percent smaller than the standard
- context diff output. Context diffs show changes made to a file by outputting
- the old and new lines with several lines around them (context). diffs allow
- automated patching programs (such as patch) to find where to make changes even
- if line numbers have changed. This new format, called unidiff, includes the
- context only once, showing the old and new lines within the same "hunk" of
- output. unidiff, Volume 14, Issue 70, includes a patch to gnudiff 1.14 to
- output unidiff's, a patch to Larry Wall's patch program (PL12) to accept
- unidiff, and programs to translate to and from old style context diffs to
- unidiff's format.
- If you're using the old troff and an HP DeskJet printer, then you need
- cat2desjet. Vasilis Prevelakis <vp@cui.unige.ch> contribution takes old troff
- output (for the c/a/t phototypesetter) and converts it to a format suitable
- for the HP Deskjet. Based on the cat2lj (c/a/t to HP LaserJet) program,
- cat2desjet runs in two modes, a pure graphic bitmap mode and a soft font
- download mode. The latter supports a lower resolution draft mode whose speed
- warrants the loss in resolution for drafts (Volume 14 Issues 71-73).
- Elwood Downey <downey@dimed.com> has updated his ephem (an interactive
- astronomical ephemeris) program to version 4.21. New features include more
- magnitude models, bug fixes, a Skydome watch format, and VMS support. The
- large posting is Volume 14, Issues 76-81.
- Tired of sorting out your files in an ls listing? Kent Landfield
- <kent@sparky.imd.sterling.com> has contributed lc, which separates the files
- into groups of like types and then displays the information in columns (Volume
- 14, Issues 82 and 83). Note for Xenix users, lc is not the same as lc under
- Xenix, which is just ls -C.
- For those into AI, Donald Tveter <drt@chinet.chi.il.us> has contributed the
- routines he used for the experiments in his AI textbook. The routines perform
- back-propagation using a very efficient algorithm. Fast-backprop is Volume 14,
- Issues 84-87.
- Seeing your news build up and wondering in what groups they reside? Tired of
- seeing du count the subdirectories in the parent directory? Then Chip
- Rosenthal's <chip@chinacat.unicom.com> enhanced du (disk usage) is for you. A
- simple useful tool that is Volume 14, Issue 88.
- David Kirschbaum <kirsch@usasoc.soc.mil> contributed a portable unzip 3.1 for
- Volume 14, Issue 102-106. Unzip from the INFO-ZIP project extracts files
- stored in PKZIP format from UNIX, Minix and Atari. It works on both little and
- big endian machines and on machines with different word lengths (except for
- the Cray's). Unzip only extracts files. It cannot build zip files.
- The system V enhanced getty, previewed in the first quarter of 1990, has been
- released by Paul Sutcliffe, Jr. <paul@devon.lns.pa.us> as version 2.0. It
- supports both standard (getty) and bi-directional (uugetty) modes, use of
- runtime defaults file, modem chat scripts to set up the modems, and incoming
- autobauding via the connect string. It's Volume 15, Issues 4-8.
- If you aren't connected to Internet, you can't run NTP (network time protocol)
- to automatically set your UNIX system to accurate time. However, you can
- install a Heath GC1000 WWV radio clock and run gc1000 to set the UNIX time
- from the clock. When run periodically from cron, gc1000 will synchronize the
- system time with the clock, correcting for drift. gc1000 does not change the
- time if the clock has lost the WWV signal, or if the time is off by more than
- a certain number of minutes (to allow for errors in the time reception not to
- effect the system). It also supports a debugging mode for checking the
- interface. It's a relatively small program and is Volume 15, Issue 9.
- Those sites running an archive and desiring a mail-based archive server should
- obtain rnalib2 by Paolo Ventafridda <venta@i2ack.sublink.org>. An advanced
- mail-server, rnalib2 allows remote users to ask for text or binary files via
- ordinary e-mail. It can compress/uuencode/btoa/uusend/e-mail or uucp a file as
- needed (Volume 15 Issues 10-13).
- gnuplot, the command-line driven interactive function plotting utility, has
- been upgraded via a very large patch. Besides bug fixes, it includes many new
- output drivers including X11. Contributed by Russell Lang
- <rjl@monu1.cc.monash.edu.au>, it is Volume 15, Issues 15-19.
- If you've ever considered moving from SCCS to RCS (see my column in CUJ,
- vol.7, no.4), sccs2rcs_kc from Kenneth H. Cox <kenstir@viewlogic.com> will do
- the trick. It converts the SCCS s-file into the identical RCS history file
- without altering or deleting the sccs file. Date, time, author, comments, and
- branches are all preserved (Volume 15, Issue 22).
- GNU-Emacs's calculator has been updated, and the update patch is more than
- 1Mb. This 20-part patch file takes calc from 1.04 to 1.05. David Gillespie
- <daveg@csvax.cs.caltech.edu> made it up, and if you have ftp access, follow
- his instructions, ftp the full source. It must be easier than making sure a
- 1Mb patch all worked. If not, it is Volume 15, Issues 28-47.
- If you keep a file as a bunch of index cards and are looking for a way to
- easily do the same on the computer, you need cardfile. Dave Lampe
- <dplace!djl@pacbell.com> contributed cardfile as Volume 15, Issues 49-51. It
- uses the metaphor of a stack of index cards with fields and subfields.
- Searching and simple formatting are supported.
- Last time I mentioned dmake, an enhanced make like utility at version 3.5,
- there was a copyright problem in that version, and the author has corrected it
- and released version 3.6, requesting that all copies of 3.5 be deleted. If you
- have copies of 3.5, please comply. The new version has no loss in function.
- Dennis Vadura <dvarudra@watdragon.waterloo.edu> has contributed the new
- version for Volume 15, Issues 52-77. A patch to dmake 3.6, patch1, was posted
- on Nov. 1, 1990 to comp.sources.bugs. This five-part patch fixes minor UNIX
- problems and major problems with Microsoft C 6.0 and TCC 2.0. dmake is
- different from other makes because it supports significantly enhanced macro
- facilities, enhanced inference algorithms, support for file system transversal
- both during inference and during the make, parallel makes on those
- architectures that support it, attributed targets and text diversions. It is
- portable to both UNIX and DOS and includes support for command.com and the MKS
- korn shell under DOS.
-
-
- Rich Is Still Slowly Posting Sources...
-
-
- Slowly, but still there have been postings appearing in comp.sources.unix, but
- they have been few and far between.
- Chip Salzenberg's Deliver program was upgraded in a 10-part patch. Deliver
- will automatically handle sophisticated algorithms for handling inbound
- electronic mail. Deliver can save the electronic mail in different files, and
- answer it for you automatically. New features include normal and error logs,
- aborting of overly recursive delivery invocations and more configurability,
- plus bug fixes. The original posting was at patchlevel 1, so this patch, patch
- 2 is Volume 23, Issues 1-10.
- If you are feeding network news a large number of sites, and running out of
- cycles, newsxd from Chris Myers <chris@wugate.wustl.edu> will help you control
- the load. newsxd is a configurable daemon that allows for a more controlled
- execution of nntpxmits and sendbatches. It can vary when they execute by time
- of day, type of service, and system load. Newsxd is Volume 23, Issues 11-13.
- For a simple spreadsheet program that is free, try sc, now up to version 6.8.
- Contributed to Volume 23, Issues 23-25 by Jeff Buhrt <sawmill!buhrt>, sc now
- supports ASCII files via psc, plus cleans up many bugs. Patches to this have
- appeared in comp.sources.bugs including files posted on Sep. 27, Oct. 29, and
- Oct. 31. These patches take sc to version 6.10. Eventually these patches will
- appear in comp.sources.unix.
- Pseudo-terminals are a method of running a process without having a real
- terminal connected all the time. Dan Bernstein <brnstnd@kramden.acf.nyu.edu>
- has contributed pty. Pty is the sole interface between pseudo-terminals and
- the rest of UNIX. It supports improved security, disconnect and reconnect,
- full pty control and several utilities. It does not yet support POSIX ptys,
- which differ slightly from the older BSD ptys, but port is planned. Pty is
- Volume 23, Issues 31-36.
- Paul Vixie <vixie@vixie.sf.ca.us> has contributed the cron, which he also
- contributed to Berkeley for 4.4BSD. This version of cron supports the System V
- style of multiple cron tables, one for each user running cron. (Cron is the
- UNIX time-based scheduling program. It runs programs at specific regular
- intervals, such as once a day at 3:00 am, or every five minutes, or the first
- Monday of every month.) Vixie-cron is Volume 23, Issues 28-30.
- The latest version of flex, a freely distributable replacement for lex, the
- Unix lexical analyzer is Volume 23, Issues 37-46. Version 2.3 at patchlevel 6
- supports UNIX, Atari, MS-DOS and VMS. It's contributed by Vern Paxson
- <vern@cs.cornell.edu>.
-
-
- Fast Update Cycles Getting Faster
-
-
- The wonder of the "net" is that support is so fast, new versions appear before
- I can even get the old versions mentioned in the column. The group
- comp.sources.games is a wonderful example.
- In my last column, I introduced vcraps, the full screen casino-style craps
- game based on curses. Now Robert Steven Glickstein <bobg+@andrew.cmu.edu> has
- updated it to version 2, which incorporates all the patches and more fixes. It
- is Volume 11, Issues 48 and 49.
- The othello-like game Reve, has been upgraded to version 1.1. It supports
- SunView, XView, X11 and termcap modes. Reve offers nine levels of difficulty,
- from beginner to tournament. The authors plan to enter the program in the
- University of Waterloo Computer Science Club's Seventh Annual Computer Othello
- Tournament. Rich Burridge <rburridge@sun.com> and Yves Gallot
- <galloty@cernvax.cern.ch> submitted it for Volume 11, Issues 52-58 with patch
- 1 in Volume 11, Issues 61-64.
- Another multi-user game is Internet Go, which allows users to play go in real
- time over a TCP/IP network. It requires a BSD style UNIX system and INET
- sockets. Igo is contributed by Greg Hale <hale@scam.berkeley.edu> and Adrian
- Mariano <agrian@milton.u.washington.edu> for Volume 11, Issues 66 and 67.
- Ski, a skiing game from Mark Stevans <resumix!stevans@decwrl.dec.com> in
- Volume 11, Issue 69, has been updated again, this time to version 6.0.. This
- text-based skiing simulation offers an infinite slope, trees, ice, bar ground,
- and the snowman. The goal is to get as far down the slope as you can.
- Unfortunately, your jet-powered skis only go on backwards so you cannot see
- where you are going, only where you have been. It has some strange options,
- and apparently is still played enough, even after eight years, to warrant an
- update.
- Tired of loosing at nethack? A spoiler's file was contributed by Alan Light
- <wheaton!alight>, Ken Roth <wheaton!kroth>, and Paul Waterman <wheaton!water>.
- It was compiled by playing nethack, and from electronic mail and news groups.
- Warning, this is a spoiler file, so if knowing the answer in advance ruins the
- fun, don't get Volume 11, Issues 71-74.
-
-
-
- Previews From alt.sources
-
-
- Calendar programs still abound in alt.sources. New this time are a non
- postscript version for use on line printers using 132x66 sized paper. Calendar
- from Andrew Rogers <rogers@sud509.ed.ray.com> generates one month per page and
- was posted Sept. 27, 1990. Pcal has also been updated to version 2.5 and now
- supports having the left-most day of the week be user-selectable, some
- reformatting of the calendar and cleanup of the resulting postscript output.
- By making the day numbers smaller, nine lines of text are possible for each
- day. The newest pcal was posted by Joseph A Brownlee <jbr@cbnews.att.com> on
- Oct. 6, 1990.
- If your old C compiler only supports eight-character variable names, then
- unique-nms posted by Jean- Pierre Radley <jpradely!jpr> can help. It creates a
- mapping of long names to short ones. Unique-nms was written by the late Fred
- Buck. It was posted on Oct. 14, 1990.
- A useful utility, cktar, will compare the contents of a tar archive against
- the actual disk files. Useful to see what has changed without un-tar-ing the
- entire archive into a parallel directory and doing a compare. It also checks
- for file ownership and permissions. It is bytewise compare program, not a
- source diff, so it also handles binary files. Cktar was posted by Warren
- Tuckey <wht@n4hgf.mt-park.ga.us on Nov. 10, 1990.
- If you need to read from compressed files, Graham Toal has provided a library
- routine set to open, read, and close compressed files. It works on UNIX and
- DOS and even supports 16-bit compress under DOS. Included is a version of
- zcat.Zlib was posted on Nov. 15, 1990.
- Still using CP/M? How about a CP/M emulator. It allows a UNIX system to
- emulate an 8080 running CP/M. It was posted on Nov. 15, 1990 by D'Arcy J. M.
- Cain <druid!darcy> in three parts.
- Psroff, the old-troff (c/a/t version) to postscript converter was updated to
- version 2.0, patchlevel 5 in a 16-part posting (complete release, not a patch)
- by Chris Lewis <ecicrl@clewis>. It supports Postscript, HP Laserjet, and HP
- Deskjet. Psroff was posted on Nov. 17, 1990.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Editor's Forum
- Greetings from the Land of Oz. That's what many Australians call their
- country, with their usual mixture of happy brashness and tongue-in-cheek
- self-deprecation. They call themselves Aussies, pronounced OZ-ees, not AW-sees
- -- which tells you why they claim Dorothy's dreamland as their own.
- I have been here just over a week now. Already, I am growing comfortable with
- driving on the left. I take it for granted that mine is the funny accent, not
- everyone else's. (Moving north to Massachusetts a decade ago gave me a taste
- of that experience.) I still marvel at swimming in the South Pacific when it
- is snowing back home. I never quite adjust to seeing Orion upside down or
- steering by the Southern Cross instead of the Little Dipper.
- My computers arrived intact. It took but an hour to locate power cords to get
- their batteries recharging. Within a day or two, I got the funny phone
- connector that put my modem back in touch with the world. Two days later, I
- had the cables I needed to drive the laser printers I'll be using here. My
- good friend John O'Brien, of Whitesmiths, Australia, gets credit for all those
- hardware successes.
- Software has been more of a problem, but not insurmountable. My e-mail now
- gets as far as John's computer across town. It is still a patchwork to get
- mail those last few kilometers. I paid an earl's ransom for Bitstream fonts
- that refuse to slide down a serial cable because they were installed with a
- parallel interface in mind. Twice I have made frantic calls to the U.S. to
- have someone read passages from manuals I thought I could leave behind.
- It is still a miracle to me how quickly I reestablished diplomatic relations
- with the other side of the world. Faxes, modems, second-day package delivery,
- MS-DOS, Windows, and UNIX all work much the same here as back home. Many
- things cost more, a very few cost less. Already, I am back in the business of
- editing and writing.
- Of course, I couldn't spend a year here without a lot of help back in Kansas.
- Look at all those other names on the masthead. They have to work that much
- harder because their editor is a bit further out of reach. I can look out my
- office window in our rented house on Mackenzie Point and watch surfers dashing
- into Tamarama Bay. Occasionally, that inspires a twinge of guilt that my
- decision to come here has demanded so much help from others. (Only
- occasionally, mind you.)
- There's no place like home, to be sure. But it's also nice to go over the
- rainbow at least once in your life.
- P.J. Plauger
- pjp@plauger.uunet.com
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- New Releases
-
-
- Updates
-
-
-
-
- CUG155 B-Tree
-
-
- Michimasa Honma (Japan) has updated CUG#155 Btree Library programs (developed
- by Ray Swartz). The code conforms with ANSI C and is tested under MS-DOS using
- Turbo C v2.0. For testing purposes, utilities that generate random numbers and
- create a B-Tree are also added.
-
-
- CUG328 WTWG
-
-
- Although WTWG, window routines for text and graphics, was introduced as a
- shareware package, David Blum (CA), the author of the package, has placed the
- C source code in the public domain. The volume now includes the source code
- for Turbo C v2.0 and Microsoft C v5.1.
-
-
- New Releases
-
-
-
-
- CUG335 Frankenstein Cross Assemblers
-
-
- Mark Zenier (WA) has submitted a collection of cross-assemblers written in the
- combination of Yacc and C. Inspired by Will Colley's cross- assemblers (CUG
- #149, #219, #242, #267, #276), Zenier has created a series of cross-assemblers
- for 8- and 16-bit microcomputers; RCA 1802-1805, Signetics/Phillips 2650,
- Hitachi 6301-6303, 64180, Mos Technology/Rockwell 6502, Motorola 6805, 6809,
- 68hc11-6801-6800, Texas Instruments tms7000, Intel 8041-8048, 8051, 8085,
- 8096, Zilog Z8, Z80.
- The assemblers provide hex listing, symbol table listing, debug, and processor
- selection. The output source is similar to Colly's, with a few minor
- differences: the end statement syntax and extensions (the support for
- different character sets and the \ escapes in strings). The front end of each
- assembler is implemented using Yacc/Bison, while the back end is written in C.
- If you replace the front end, you will get a new cross-assembler. The package
- includes Yacc and C source code, documentation, testing input and output
- files, and makefile. The programs were developed and tested under UNIX/Xenix
- and MS-DOS systems. Turbo C v1.5 was used for MS-DOS. Yacc or Bison (CUG #285)
- is required to build an executable code.
-
-
- CUG336 EGAPAL/EDIPAL
-
-
- This volume includes EGA graphics applications and utilities contributed by
- Scott Young (NH) and Marwan El-AUGI (FRANCE). Young has submitted a shareware
- package, EGAPAL, which is a series of programs allowing users to create EGA
- graphics images for the 640x350 and 16-color mode. EGAPAL includes a graphics
- image editor program that lets you select a color from the palette, draw an
- image on the screen, and save it. EGAPAL also includes a utility that converts
- the graphics image into a header file to be included in your C programs, and a
- library that loads a graphics image from disk of header files to the screen.
- The package includes a documentation and sample program. Turbo C is required.
- For registration, please contact the author (P.O. Box 1550 Section #8,
- Portsmouth, NH 03802).
- EI-AUGI has submitted a palette editor, EDIPAL, which allows the user to
- change the EGA palette and save it. Saving the new palette is implemented by
- not closing the graphics system, therefore the change is not permanent. (He is
- working on this problem.)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- New Products
-
-
- Industry-Related News & Announcements
-
-
-
-
- UNIX System Laboratories Offers New Products
-
-
- UNIX System Laboratories is offerring the latest releases of its Open Look
- graphical user interface (GUI), Xwin graphical windowing system and Alex
- developer's tool software.
- Open Look GUI for UNIX System V Release 4, and Xwin graphical windowing
- system, USL's implementation of the X Window System ported to System V, are
- available individually or together. Together, they are offered as Graphics
- Services v4. Alex software, which is a language extension to X developer's
- tool, is available separately.
- Open Look GUI contains a customizable environment featuring icons, pushpins,
- pull-down menus, point-and-click desktop and file manager, and other
- utilities. Open Look GUI works with any implementation of the industry
- standard Massachusetts Institute of Technology X Window System Intrinsics
- Version X11R4.
- This release of Open Look offers realistic three-dimenstional visuals that
- provide enriched appearance and improved visual feedback to user input.It also
- enables users who choose not to use a mouse to traverse the screen by rising
- keyboard accelerators or navigational keys.Release 4 of Xwin includes support
- for multiple servers, allowing users to run a server in each virtual terminal
- or on multiple terminals connected to a single CPU.
- Graphic Services v4, Open Look GUI, and Xwin graphical windowing system are
- available now. Source licencing fees for Graphics Services v4 are $20,000 for
- the initial CPU. Current customers can upgrade through April 1, 1991 at a 50
- percent discount.
- For more information, contact UNIX System Laboratories at (800) 828-UNIX.
-
-
- MIPS Introduces International RISC/OS
-
-
- MIPS Computer Systems has introduced an international version of its RISC/OS
- UNIX-based operating system. In addition, MIPS introduced a Japanese country
- kit, which provides users with a Japanese language interface for MIPS
- workstations and servers.
- RISC/OS 4.51 employs a layered design, making the system readily adaptable to
- various languages and simplifies the development required to internationalize
- applications. The lower layer provides functionality that is independent of
- any one specific language or culture. The upper layers serve as country kits
- that provide language and cultural-dependent interfaces.
- MIPS implemented its first localized country kit for Japan, including messages
- translated into Japanese for more than 130 UNIX commands. Country kits in
- other languages will follow. The kit provides a Japanese version of the X
- Window System terminal emulator, so Japanese characters can appear on display
- screens in the X Window System environment. The kit also provides a Japanese
- version of the EMACS text editor.
- For more information, contact MIPS Computer Systems, 928 Arques Ave.,
- Sunnyvale, CA 94086-3650, (408) 720-1700; FAX (408) 991-7777.
-
-
- Multiprocessing Development Environment for 68000 Family
-
-
- An integrated development environment for developers of multiprocessing
- embedded systems based on the 68000 family of microprocessors is now available
- from Intermetrics Microsystems Software an Precise Software Technologies. The
- new environment incorporates the InterTools embedded systems development tools
- with the Precise/MPX multiprocessing kernel.
- Precise/MPX is a multiprocessing real-time kernel for industrial embedded
- systems. It is an open kernel and can be used with an unlimited number of
- different processors. MPX is based on a high-performance message passing
- paradigm and supports multiple tasks on each processor.
- The InterTools software development package features extensive C and
- target-level optimizations and ANSI C language support. This package includes
- programming utilities and XDB, and a multi-window source level debugger.
- Prices for the integrated multiprocessing application development environment
- start at $15,000 and will vary depending on host system and target platform
- configuration. For more information, contact Intermetrics Microsystems
- Software, 733 Concord Ave., Cambridge MA 02138-1002, (617) 661-0072; FAX (617)
- 828-2843.
-
-
- Qtool Offers Code Analysis System
-
-
- CAS is an integrated, interactive tool system that will analyze, maintain, and
- enhance ANSI C and C++ on DOS. CAS, which is available from Qtool, works with
- any C compiler system. The system has four tools: a code browser, a text
- browser, a command line query utility, and a translator.
- The code browser helps programmers locate identifiers, defines, numbers, and
- include in a project with query results placed in a scrollable list. The text
- browser has built-in language capabilities and also allows hypertext-like
- database searching. You can trace function calls or identifier references
- across the project and also automatically trace include files. Both the text
- browser and code browser permit easy invocation of external editors and other
- tools. The command line query utility allows database queries from the command
- line similar to interactive querries from within the code browser.
- CAS is available for the PC compatible on DOS. The price is $100. For more
- information, contact Qtool, 5814 SW Taylor, Portland, OR 97221, (503)
- 297-3583.
-
-
- Fast Floating Point Library
-
-
- Triakis is now offering a library of C compatible functions called FFP (Fast
- Floating Point), a floating-point arithmetic method based on the
- sign-logarithm number system. The library operations, which can often rival a
- math coprocessor in speed, can broaden the appeal of an application program
- for users without special math hardware. The precision and exponent range of
- the numbers equal and slightly exceed standard single precision.
- Applications include general scientific computation, geometrical graphics,
- signal processing, simulation, and non-linear systems. The library includes
- transcendental functions, logarithm, exponential, and square root.
- The library functions are compatible with Quick C and Turbo C. The product
- sells for $59. A small royalty is charged when it is used in a commercial
- product. For more information, contact Triakis, 1011 Duchess Rd., Bothell, WA
- 98012, (206) 486-8282.
-
-
- Application Development Tool For Motif
-
-
- A Motif-based graphical user interface development tool is now available from
- JYACC. The tool gives developers the flexibility of presenting their
- applications forms or objects in either graphical or character-based modes
- without recompiling. It also provides seamless integration to multiple
- databases.
-
- The tool provides support for basic Motif features, such as text, scrolling,
- and push button widgets, full mouse and graphics support, and proportional
- fonts.
- For more information, contact JYACC, 116 John St., New York, NY 10038, (212)
- 267-7722; FAX (212) 608-6753.
-
-
- Symbolic Debugging For 6805 Chips
-
-
- Byte Craft Limited has released v1.0 of its embedded systems development
- productivity tool for debugging, integrating, and testing code. The E6805
- Symbolic Host Support works as a software connector between the 6805 emulation
- board and the C6805 Code Development System's C compiler, and it adds symbolic
- debugging capabilities to the M68HC05 EVM and EVS boards.
- The E6805 can step through source code at either instruction rate or source
- line rate. Programs may be traced following the data flow or following the
- program logic. Listing displays trace the activity of the program being
- executed on the emulator or respond to user needs through browser commands.
- The E6805 sells for $295 and requires 512K memory. For more information,
- contact Byte Craft Limited, 421 King Street North, Waterloo, Ontario, Canada
- N2J 4E4, (519) 888-6911; FAX (519) 746-6751.
-
-
- Lynx Shipping Real-Time POSIX OS
-
-
- Real-time UNIX applications developers can start developing portable
- applications that comply with the emerging POSIX 1003.4 real-time standard
- with the release of LynxOS 2.0 from Lynx Real-Time Systems.
- LynxOS 2.0 also provides real-time Ada implementations based on lightweight
- threads as described in POSIX 1003.48 (Draft 3). LynxOS 2.0 will sell for the
- same price as previous versions, which vary according to hardware platforms
- and optional features, such as X Windows, Motif, NFS, or ROMkit. A typical
- development version for 80386 PC/AT compatibles, (including bootable LynxOS
- kernel, shell, utilities, libraries, and object files to customize the LynxOS
- kernel, C compiler and library for software development, device driver source
- code, and user manuals costs $1,495.
- LynxOS runs on the Intel 860i, 80386 SX and DX and 80486, Motorola 680X0 and
- 88000, and MIPS R3000 and R6000 microprocessors. For more information, contact
- Lynx Real-Time Systems, 16780 Lark Avenue, Los Gatos, CA 95030, (408)
- 354-7770; FAX (408) 354-7085.
-
-
- AGE Establishes Independent System Testing Lab
-
-
- AGE has established an independent laboratory for X Window System testing and
- evaluation.
- The new service, called AGE Labs, provides the personnel and equipment
- necessary to perform comprehensive X Window System product testing,
- benchmarking, and analysis. This includes access to DECstation,
- Hewlett-Packard 3XX and 8XX, IBM RS/6000, Sun 3, Sun SPARCstation,
- 386/Interactive UNIX, 386/SCO UNIX, and Open DeskTop, and VAXstation
- workstations; interfaces for Ethernet, IEEE 802.3, Token Ring, and serial
- communication ports; Internet (TCP/IP), SL/IP, and DECnet networking
- protocols, multi-user operating systems (UNIX and VMS); and X user
- environments such as Motif, DECwindows, and OpenWindows.
- Evaluation includes use of the Xlib Protocol Test Suite (T7), the MIT Volume
- Stress Test, and the AGE Test Suite, Testing is also done using standard X
- client programs and with popular commercially available X Window System
- applications.
- The multi-vendor, heterogeneous hardware and software environment ensures
- thorough hardware and software testing. The AGE Labs service is available to
- all vendors and is operated independently from the development teams at AGE.
- AGE assures confidentiality of all products.
- For more information, contact AGE, 8765 Aero Drive, Suite 226, San Diego, CA
- 92123, (619) 565-7373; FAX (619) 565-7460.
-
-
- Software Measurement Package For UNIX Updated
-
-
- SET Laboratories has released v2.0 of UX-Metric line of software measurement
- tools for Ada, C, and C++. UX-Metric computes measures of how difficult a
- comptuer program will be for a programmer to understand, test, or modify. The
- release adds support for additional metrics as well as a number of other
- enhancements.
- UX-Metric uses techniques known as software complexity metrics to measure the
- characteristics of a program's source code, which are known to cause trouble
- for programmers. In addition to support for the software science, cyclomatic
- complexity and span of data reference measures, v2.0 also measures number of
- blank lines, number of comments, and average variable name length.
- UX-Metric v2.0 sells for $450. Registered users can upgrade for $50. The
- company offers a 30-day money back guarantee. SunOS and System V/386 versions
- of UNIX are currently supported. For more information, contact SET
- Laboratories, P.O. Box 868, Mulino, OR 97042, (503) 829-7123.
-
-
- Sage Updates Demo II
-
-
- Sage Software is now shipping Dan Bricklin's Demo II v3.0. This version allows
- users to overlay text on bitmapped images. It also adds mouse support, extends
- keyboard support to AT-type enhanced keyboards, adds VGA video support,
- provides an autosave feature, and adds memory swapping that lets Demo II
- shrink to 7K on execution of external programs.
- Demo II is a PC-based development tool for producing program prototypes,
- demonstrations and tutorials. Demo II simulates the operation of any program
- under any computing environment. It includes a run-time module with unlimited
- distribution licensing so demonstrations produced can be freely sent to
- prospective software users, providing demonstrations of a program's operation
- without supplying any live code.
- Demo II v3.0 is available in a single user version for $249. For more
- information, contact Sage Software at (800) 547-4000; FAX (503) 645-4576.
-
-
- Source Level Symbolic Debugger
-
-
- Sierra Systems has introduced QuickFix, a high-end C language source-level
- symbolic debugger for the 68000 family. The debugger's advanced
- hardware/software design complements the company's previous product, the
- Sierra C cross-development compiler for embedded applications.
- The debugger offers a quick-connecting parallel ROM communication cable,
- convenient multi-window display, and commands featuring a C-like syntax for
- extremely intuitive operation.
- QuickFix's high-speed performance can be attributed to the parallel ROM
- communications interface. The debugger has six display windows: Source Window,
- Register Window, Command Window, Data Display Window, Terminal Emulation
- Window, and Help Window.
- QuickFix command expressions follow C language syntax and semantics.
- Expressions can incorporate any C operator, including casts and functions
- calls. All local and global variables can be referenced by their symbolic
- names, and the stack trace can be examined to determine the exact function
- nesting. For repetitive operations, debugger commands can be grouped into
- macros or placed into batch command files.
- With QuickFix, programmers can observe, control, and modify embedded
- applications as they execute on the target system. The debugger can set
- breakpoints in either ROM or RAM, set watchpoints, display and modify register
- and memory contents, and trace program execution. Conditional breakpoints and
- code patching allow users to test bug fixes quickly and efficiently without
- recompiling and reloading the target program.
- The QuickFix debugger hosted on PC AT computers, including the target systems
- monitor in source form and ROM communication cable, costs $1,500 when
- purchased with the Sierra C Compiler. When purchased separately, QuickFix
- costs $1,750. For more information, contact Sierra Systems, 6728 Evergreen
- Ave., Oakland, CA 94611, (415) 339-8200; FAX (415) 339-3844.
-
-
- C Programmer's Toolbox
-
-
-
- The C Programmer's Toolbox v2.1 is now being shipped for Apple's Macintosh
- Programmer's Workshop, PC/MS-DOS, and Sun's UNIX development environments. The
- Toolbox, from MMC AD Systems, is a collection of 24 programming tools and aids
- that enhance programmer productivity, while improving program performance and
- quality. Major tools include CFlow, CLint, Cpp, CPrint, CXref, PMon and more.
- This release adds support for Microsoft C 6.0 and Think C 4.000Ps. New options
- include ANSI function prototype generation for any C source code, construction
- of composite include files, new file/function interdependency reports, ne C
- source code formatting options, and external specification of hidden include
- files.
- The Toolbox works with any C dialect, and works with any size program through
- its virtual memory system and wildcard/indirect file processing facilities.
- For more information, contact MMC AD Systems, Box 360845, Milpitas, CA 95036,
- (415) 770-0858.
-
-
- Kozo Signs SilverScreen Development Pact
-
-
- Kozo Heikaku Engineering and Schroff Development have signed a software
- development contract under which Schroff will implement Pixar's RenderMan and
- the Phar Lap's DOS extender on SilverScreen.
- SilverScreen is a 3-D CAD/Solid Modeling software system. It features 2-D and
- 3-D Boolean operations, rendering, shading, mass properties, and associative
- dimensioning. SilverScreen runs on DOS and UNIX (Silicon Graphics), For more
- information, contact Schroff Development, P.O. Box 1334, Mission, KS 66222,
- (913) 262-2664; FAX (913) 722-4936.
-
-
- Cadre Unveils Teamwork
-
-
- Cadre Technologies has released Teamwork 4.0 to supply computer-aided software
- engineering (CASE) tools for C, Ada, and C++ applications development. These
- CASE products address each phase of the software development process from
- initial specifications to implementation and are complemented by advanced
- testing capabilities throughout the entire life cycle.
- New additions include dynamic modeling and real-time simulation, reverse
- engineering C code, an Ada design-sensitive editor, expanded object-oriented
- analysis support, heterogeneous network support, a document production
- interface and a new release of PathMap.
- Cadre Technologies has also introduced SaberLink, an operational interface
- between Saber Software's Saber-C program development environment and Cadre's
- Teamwork family. SaberLink allows users to invoke Saber-C commands from
- Teamwork's structured design view.
- For more information on either of these products, contact Cadre Technologies,
- Providence Division, 222 Richmond St., Providence, RI 02903, (401) 351-5950;
- FAX (401) 351-7380.
-
-
- AccSys Expands Support
-
-
- AccSys for Paradox has expanded its capabilities to include C and C++ support
- for the Borland Turbo and Zortech compilers. Programmers can use v2.5 of
- AccSys for Paradox to develop an application that runs under Microsoft's
- Windows 3.0 and OS/2 using the Microsoft C compiler.
- AccSys, from Copia International, is a seamless interface between C and
- Paradox table files, and primary and secondary indexes and sorts database
- files. Even though Paradox offers a versatile high-level language interface,
- it does not provide the flexibility or the efficiency of C or C++. AccSys and
- Paradox can be accessed either from your own application or from within
- Paradox.
- A complete set of working examples is included with AccSys for Paradox v2.50.
- Source code can be purchased at an additional cost. There are no runtime
- royalties, and the product is backed by a full 60-day return policy. Prices
- for a single user copy is $395, with source code, $795. A multi-user copy is
- $750, and $1,500 with source code.
- For more information, contact Copia International, 1964 Richton Drive,
- Wheaton, IL 60187, (708) 682-8898; FAX (708) 665-9841.
-
-
- C-terp Employs DOS Extender Technology
-
-
- Gimpel Software has release C-terp and C-terp 386 v.3.5. These C interpreters
- offer integrated debugging and editing and employ Phar Lap DOS extender
- technology to access all available extended memory.
- C-terp features full K&R support with ANSI extensions, a full-screen,
- built-in, reconfigurable editor, incredibly fast semi-compilation, complete
- multiple module support, automatic make file, global search, preprocess
- facility, system shell, trace, batch mode, and full-screen source-level
- interactive debug facilities.
- Under MS-DOS, C-terp is available for the Microsoft 5.x and 6.0 compiler for
- $298. C-terp 386 is compiler-specific. In the Phar Lap environment, it is
- available for Meta Ware's High C 386 and Watcom C 386. It sells for $398 for
- the first copy and $300 for each additional copy. C-terp 386U, for UNIX System
- V/386, is also available.
- For more information, contact Gimpel Software, 3207 Hogarth Lane,
- Collegeville, PA 19426, (215) 584-4261.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- We Have Mail
- Dear Bill,
- I just read the December issue of the CUJ and thought it was quite good. It
- had more useful information than most of the CUJ editions I've read.
- Listing 1 in Rex's article is something I've always been amused with. I even
- thought of a few more that are even less obvious:
- a[i] == i[a]
- == *(a+i)
- == (a+i) [0]
- == 0(a+i)
-
- a[i] [j] == 0[a+i] [j]
- == j [0[a+i]]
- == 0[0[a+i] + j]
- It seems pretty weird to me.
- Second, there was a letter from Firdaus Irani asking about:
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
-
- int comp(unsigned char**, unsigned
- char **);
- unsigned char *list[] = { ... };
-
- main(){
- int x;
-
- qsort(list, 5, sizeof(unsigned
- char *), comp);
- ...
- }
- I tried this with our compiler:
- Cray Standard C, Release 2.0.1.2,
- P/YMP Version
- C Front-end Version 059 CMCS
- Back-end Version 386
- Compiler Generation Date = Nov 1 1990
- Compilation Date and Time = Thu Nov \
- 29, 1990
- 09:34:04
-
- *** *** SC151 [warning ] File = \
- yy.c, line = 11:
- The function call argument #4 is not \
- assignment compatible.
-
- TOTAL WARNINGS DETECTED IN yy.c: 1
-
- Compilation Execution Time = 0.560350
- seconds
- which behaves the same as the Borland compiler (except it's a warning).
- My reading of the standard is that this is correct. The function comp must be
- assignment compatible with the fourth argument to qsort:
- extern void qsort(void *_Base,
- size_t _Nmemb, size_t _Size,
- int (*_Compar)(const void *,
- const void *));
- which is the last line. If this were allowed to work as it stands then
- int (*pf)(void *, void *) = comp;
- must also work. I don't see that the rules for assignment compatible allow you
- to look arbitrarily deep into the type that is pointed at and allow all
- pointers to void to be compatible with any old pointer. I think an easier way
- to state this is:
-
- char **q;
- void **p;
-
- p = q; /* not assignment compatible */
- When trying to determine assignment compatibility of pointers, you look at the
- pointer and what it points at. Everything else must be compatible (as opposed
- to assignment compatible).
- This was a compromise we arrived at when trying to figure out what to do with
- things like
- int **p;
- const int **q;
- We decided that an implementation only needs to look one "type part" back for
- assignment compatibility. After that they have to be strictly compatible.
- That's my recollection anyway. Talk to you later.
- Tom MacDonald
- Cray Research
- Thanks, Tom. (To our other readers -- Tom MacDonald is one of the more active
- contributors to the standardization and evolution of the C language. He made
- major contributions to the floating-point specifications in <float.h> and
- <math.h>, among other thing. He also participates in the on-going work of the
- Numerical C Extensions Group.)
- The definition of the array subscript operator leads to all sorts of craziness
- if you depart from the most conventional usage. None of the forms you and Rex
- Jaeschke explore are necessarily "wrong," but they can sure mislead.
- Your analysis of type compatibility among pointers to pointers is on the
- money. I believe Tom Plum anguished quite a bit about the problems the C
- standard causes with bsearch and qsort users. I don't think the rules we ended
- up with are either incorrect or onerous, but they do make us old-line,
- seat-of-the-pants C programmers think a bit harder. -- pjp
- Dear Sirs
- I was left in a most confused state after having read the review of Menuet by
- Mr. Volkman in "Quick Takes" in the CUJ, Vol. 8 No. 12, pp. 101. Correct me if
- I err but, in contrast to the conclusion reached by Mr. Volkman, is it not
- desirable that a GUI not provide intertask comunication and other attributes
- associated with an operating system?
- Perturbed,
- J. Ue Parrot
- Chicago, IL
- The distinction between a GUI and an operating system can be pretty academic.
- If you want to support multiple window, cutting and pasting between windows,
- and background processing, your display manager had better at least be on
- intimate terms with the machinery that shuffles processes.
- The functions of GUI and scheduler are distinct. Each is complex and difficult
- to implement. Each has serious performance requirements. All that argues for
- keeping the two functions distinct, as you point out. In real life, however,
- designers have yet to do so without serious performance penalties. -- pjp
- Dear C Users Journal,
- I have never written to a magazine before and I usually avoid all arguments
- defending one computer language against another. But after reading Rodney M.
- Bates' letter in the January issue I feel impelled to express myself -- enough
- is enough. Comparing the latest incarnation of C (or any other language) with
- ISO Pascal is like comparing a 1990 Buick with a Model-T. Have any of you
- rabid C (or Modula-2) types looked at a Turbo Pascal compiler lately?
- Let's begin with "no Pascal that I know of has separate interface and
- implementation modules." and the "popular UCSD Pascal...force(s) massive
- recompilation." Turbo Pascal has supported private and public modules for
- years now. In the masses of code that I am currently wading through I am
- working with upwards of 50 different files in at least 3 different
- directories. Using a command line switch tells the compiler whether I want to
- "make" some of them or "build" all of them (sound familiar?). And it's fast --
- I mean fast the way no C compiler is. And that conclusion is based upon my own
- experiences using C on the same machine. (Yes, Virgina, I do program in C
- also.)
- The statement about "popular UCSD" only serves to emphasize that Mr. Bates has
- not looked at a Pascal compiler for at least 5 years. (And in this business,
- that reads 50 years.) UCSD Pascal's popularity has declined probably in direct
- proportion to the popularity of the old Apple II, and in the microcomputer
- world, Turbo Pascal is the de facto standard.
- TP 6 supports units, separate compilation, pointers to functions, bit
- manipulation, MS-DOS register types and interrupts, oh -- and of course
- objects. I could go on and on. And it's fast -- fast -- fast with a smart
- linker that ignores what you don't need to use. No inflated code here, thank
- you.
- In the same vein, years ago when I was taking a course in C, the teacher
- proclaimed that "you can't do bit manipulation with Pascal." TP has supported
- the operators shl (or <<), shr (>>), and xor (^) since day one. Okay, I can't
- define a structure composed of bit variables in Pascal, but then, neither can
- I manipulate sets in C.
- The real point -- every language has its own strengths and weaknesses. "Real
- programers" should be at least bilingual so that when the ideal development
- environment becomes available they can take full advantage of it. What
- environment you ask? -- the one where you can mix at least four languages with
- ease. Like some assembler for communication work, C for pointers to huge
- arrays, perhaps Prolog for a database and then Pascal for maintainability --
- and sheer elegance.
- Sincerely,
- Barbara Argilo
- Several dialects of Pascal make up for one or more of the deficiencies in the
- language definition put forth by Wirth. Several implementations have excellent
- development support. Some produce excellent code. The fundamental problem with
- Pascal has always been a lack of standardization for many features essential
- to serious development, such as separate compilation and manipulation of files
- by name. Your code can thrive in one environment and not survive porting to
- another.
- You don't have to be a language bigot to observe that Pascal has diminished in
- popularity over the same period that C has thrived. The bigotry appears only
- when you contrive explanations for these trends. If you argue from emotional
- bias instead of looking at verifiable facts, you indulge in a form of bigotry.
- Programmers should certainly be comfortable in more than one language, if only
- to appreciate the strengths and weaknesses of their favorite one(s). -- pjp
- Dear C Users Journal,
- There are a few minor fixes in Louis Baker's article (CUJ Jan. 1991) First,
- the correct address is Zortech Inc., 4-C Gill Street, Woburn, MA 01801. The
- correct phone numbers are (617) 937-0696, fax (617) 937-0793.
- Concerning your comments on Zortech and Turbo C++ on page 125, if you run out
- of memory, you are not necessarily out of luck. With version 2.1 and beyond,
- you can use the Rational DOS Extender technology (-br compiler flag) which
- puts the compiler into extended memory, freeing up that space for your
- program.
- On page 126, the Zortech v2.1 and above linker no longer mangles the names in
- its error messages. It is a native code linker. Also, Zortech supports the
- cdecl as well as the extern C keywords. The extern C keyword is ANSI standard.
- Marc Brockman
- Zortech, Inc.
- 4-C Gill St.
- Woburn, MA 01801
- Thanks for the clarification. -- pjp
- Dear Mr. Ward:
- Since the first C Users Journal arrived at my home, I have eagerly awaited
- every following issue. I used to subscribe to a plethora of magazines, but
- since have reduced them to this one. Good job and keep it up!
- Besides my work in MS-DOS and VAX/VMS, I have also worked on the THEOS-286V
- operating system. I have yet to see any reference to it in this magazine. If
- there are any CUG members who also use this operating system, I would love to
- hear from them.
- I also caught a reference to the BBS operated by R&D Publications. If it is
- something useful to CUG members, how do I obtain access?
- Thank you for your attention.
- Aaron N. Cutshall
- LZA Computer Resources
- 4233 Casa Verde Dr.
- Ft. Wayne, IN 46816
- THEOS-286V is a new one to me. Anybody have any information on it? As for the
- BBS, R&D hasn't put up any special customer services on it the last I heard.
- Should that come to pass, you will read about it here first. -- pjp
- Dear Mr. Ward:
- In your January 1991 issue, Tom Plum provided some insight on C compiler
- validation services being offered by a commercial company, the British
- Standards Institute. I would like to add to the information he provided by
- shedding some light on the certification process being used by our own
- government.
- Our government's National Institute of Standards and Technology (NIST),
- formerly the National Bureau of Standards, provides the compiler certification
- service for all US government agencies, such as the Departments of Defense,
- Commerce, Energy, etc. This certification is required for any vendor who
- wishes to sell to any US government agency, and is the only certification
- required by law in this country. The government uses a standard called a
- Federal Information Processing Standard (FIPS), which is usually the same as
- the corresponding ANSI or IEEE standard from which the FIPS was adopted. In
- the case of C, NIST is adopting a FIPS-C which is the same as the ANSI C
- Standard, with some additional requirements.
- NIST has been performing language certifications for years on other FIPS
- languages such as Pascal, FORTRAN, and COBOL, and will likely begin C
- certifications sometime this year. There are a number of points regarding C
- certification that your readers should be aware of:
- 1. Only NIST can perform such certification.
- 2. The NIST will use only the ANSI C validation suite (ACVS) supplied by
- Perennial as the certifying test system.
-
- 3. Those wishing to obtain certification must be ACVS licensees.
- The value of a NIST certified compiler should be apparent to anyone. I agree
- with Tom Plum that compiler vendors should take steps to achieve
- certification. However, since these certifications are not free, vendors
- should make informed decisions regarding the relative value of certifications
- and how to obtain them. It's not clear what value a BSI certification has, if
- any, particularly here in the United States.
- Sincerely,
- Barry E. Hedquist
- President
- Perennial
- 4699 Old Ironsides Dr., Suite 210
- Santa Clara, CA 95054
- The folks at BSI tell me that the U.S. government has certain obligations
- under the treaty they endorsed as a member of the ISO. One of these is to
- provide reciprocity with validation services offered by other member nations.
- You can imagine what an economic impediment small companies would face if each
- member nation could demand separate validation for products requiring
- standards compliance.
- I have even heard some Europeans argue that the shoe is on the other foot.
- Because NIST insists on producing a FIPS standard that is not exactly the ANSI
- standard, FIPS certification may not be acceptable to customers requiring ISO
- C conformance. But the U.S. government is obliged to honor an ISO
- certification from any member-authorized body.
- As for me, I dunno. I am certainly no expert on the legalities involved. I
- happily disclose that Tom Plum is more than a good friend. (Our wives are twin
- sisters.) I observe that significant profits are at stake for the small
- companies providing validation services around the world. Clout with major
- vendors and purchasers of C compilers is an important issue. I also must
- report more than a little unhappiness with the high-handed way NIST behaves in
- the standards arenas that I care about. To me, they often appear coercive,
- unresponsive to the community they supposedly serve, and more than a little
- self-serving in their financial dealings. I emphasize, however, that these are
- personal opinions.
- Thank you for writing. -- pjp
- Dear C Users Journal,
- I have recently purchased Turbo C++. What percent of your articles are
- applicable to Turbo C++? Perhaps an index of past issues may be helpful.
- Thanks.
- Gil Hoellerich
- 2617 Country Way
- Fayetteville, AR 7203
- I use Turbo C++ myself for most of my development work. Mostly, I use it for
- the ANSI C compiler hidden inside, not to mention the comfortable development
- environment on my PCs. I can tell you that essentially all the C code that has
- gone into recent issues of CUJ will work fine under Turbo C++. Code that
- cannot is usually identified as a specific dialect, or making use of
- nonstandard library functions. Quite a bit of the code we publish is actually
- developed under Turbo C or Turbo C++.
- The C++ stuff is just a bit more problematic. That language suffers more from
- dialect variations than its older brother. Extensions to the Standard C
- library, beyond the basic C++ I/O, vary the most. Nevertheless, our authors
- tend not to push into the dark corners of C++ except when they are telling
- cautionary tales.
- An index sounds like a great idea. The next time Robert finds someone sitting
- around the office looking bored, he can discuss the matter further. -- pjp
- In fact, we have several initiatives under way. We should have some news "real
- soon now." -- rlw
- Dear Editor,
- This example of implementing a state machine is a welcome article. However
- when I tried the listings example, the following errors were found:
- 1. Listing 1: MAX_FUNCS should be added as a #define with a value of 5. It is
- referenced in listing 3.
- 2. Listing 2: The Next_State values of S_PLAY under the states S_FAST_F,
- S_REWlND, and S_RECORD are wrong. The next states remain the same as present
- states.
- 3. Listing 3: In the function driver, under the "find the state" comment, a
- reference to s_table[i] is missing the underbar.
- 4. The state diagram (and table) does not allow for the case where the tape is
- left in the VCR and power turned off. When power is turned on, the tape must
- be inserted before one can get to the READY state.
- Suggestions for improvement:
- A. Listing 1: The valuef 5 in defining the state table structure (*flist[S])
- should now be changed to MAX_FUNCS.
- B. Define a MIN_CHAN (of 2) for the VCR to cycle from 2 thru 13.
- C. A follow-up article on "Alternative Organizations" mentioned by the author
- is in order.
- Enjoy the Journal -- keep up the good work!
- Don L. Jackson
- P.O. Box 681
- Gilbert, AZ 85234
- The author, Paul Fischer, replies:
- Mr. Jackson,
- Thanks for the comments. Sorry for any inconvenience errors 1-3 may have
- caused. Regarding number 4, there are two solutions.
- As I stated in the last paragraph of the section entitled "Example," a
- function should be added that causes an automatic ejection of the tape in any
- transition to the S_OFF state. I consider this function to be "internal device
- operation" and did not list it. I do agree my decision not to list it causes
- confusion on the operation of the VCR in that case.
- Alternatively, some designers prefer to move to a "transition" state and
- execute a function that checks the outstanding condition (in this case if
- there is a tape present), and then generate an internal event to move to the
- correct state and execute the appropriate functions. For this presentation I
- was attempting to keep the diagram and state table as simple as possible. For
- that reason I did not apply this alternative method.
- Thanks again for your comments.
- Paul Fischer
- P.O. Box 6 81
- Gilbert, AZ 85234
- Dear Mr Plauger:
- I read with interest your editorial in the C Users Journal of December 1990,
- particularly with respect to your forthcoming sabbatical in Australia!
- Many of my associates, not least myself, would be extremely interested in
- meeting you during your stay here. Are you going to visit Melbourne, Victoria,
- at all? While my company is particularly interested in Windows 3.0 and OS/2
- A.P.I's, we run a bulletin board which has a large and devoted group of C
- programmers. We have regular (monthly) meetings and would love to have you
- along one evening if this were possible.
- I look forward to meeting you in 1991. Please don't hesitate to call should
- there be anyway in which myself or my associates can be of assistance to you
- during your stay.
- Yours faithfully,
- Stephen Moignard
- Windows Solutions
- 1 Jamboree Ave.
- Frankston South
- Victoria, Australia 3199
- Thanks for the invite. My family and I do plan to travel about Australia, and
- I have already received a staggering number of offers to visit and speak. You
- can reach me via the Computer Science Department at UNSW. -- pjp
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- QNX Windows
-
-
- Lan Barnes
-
-
- Lan Barnes is a microcomputer systems programmer and database consultant
- working in MS-DOS and QNX. He has written a full-screen QNX editor that he is
- converting to QNX Windows in his spare time. Readers may contact him at 822
- Guilford Ave., Suite 147, Baltimore, MD 21202.
-
-
- In recent years the QNX operating system has become a favorite of systems
- designers. QNX is a networked OS (or NOS) for microcomputers capable of
- combining real-time performance with a user interface that can support
- "friendly" applications. The system has been around for awhile -- Quantum
- Software Systems, QNX's vendor, has been shipping one form or another for ten
- years. Nevertheless, QNX is less dated than many younger operating systems.
- Its underlying modular, message-passing architecture still stands as a fresh
- and powerful alternative to conventional OS and NOS designs.
- The friendliness of QNX's user interface, however, can be challenged. The
- traditional QNX shell interface can best be described as "UNIX without the
- frills." While programmers and network administrators tend to appreciate QNX's
- spare elegance, new QNX users -- even the more technical users typical of a
- real-time environment -- have always been subjected to a steep learning curve.
- In today's micro culture of graphical user interfaces, QNX developers felt an
- obvious need for a GUI for QNX.
- Such a GUI was announced and released at the QNX Developers Conference in
- Ottawa last fall. I provide here a high-level overview of that GUI, called QNX
- Windows.
-
-
- Open Look Standard
-
-
- QNX Windows is based on the Open Look standard supported by, among others,
- AT&T and Sun Microsystems. Open Look is a pleasing standard, depending heavily
- on familiar visual references to provide intuitive ease of use. For example,
- you scroll by using a slide bar. You make both exclusive and nonexclusive
- choices using buttons that invert in shadowing when you toggle them, so they
- appear depressed.
- Small, standardized icons provide features in an intuitive manner. Menus can
- be made "sticky," for example, so that they will remain in view after a
- selection, by clicking on an icon of a pushpin in the menu's corner.
- Similarly, a selection that leads to a submenu has a wedge-shaped arrowhead
- beside it, while a terminal selection does not.
- Open Look's ease of operation does not come at the expense of control
- subtlety. Consider the example of the ubiquitous slide bar. This icon, which
- looks and acts like a stereo control, can interpret a single mouse click to
- scroll:
- to the beginning
- to the end
- up a page
- down a page
- up a percent of the total
- down a percent of the total
- You can also use the mouse to drag the scroll to a specific place.
-
-
- Necessary Resources
-
-
- I confess that I found Open Look as expressed in QNX Windows to be easy and
- intuitive to pick up. For me, this a profound concession, since I have been
- described as a "mouse-hostile anti- windows curmudgeon" by my GUI-enthusiast
- friends. But the proof of a GUI is in the programming. More specifically, a
- GUI's real value is a function of the performance it provides compared with
- the resources it consumes, including that most valuable resource, programmer's
- time.
- In hardware resources, QNX Windows requires a minimum of an 80286 machine with
- 2 MB of memory and either EGA or VGA. Unlike other GUIs, this minimum
- requirement is realistic, and gives satisfactory performance. I tested the
- package on my home machine, a 286 that cruises along at a sedate 9.5 mHz with
- one wait state and hard drive seek times in the mid-50s (counting in msec.).
- This combination performed quick screen painting and smooth scrolling, even
- with a background process running.
- Quantum claims that QNX Windows will outperform such GUIs as Open Desktop on
- machines with half the clock speed and a quarter of the memory. I can't refute
- that. I know, by comparison, that Microsoft Windows 286 limps along on my
- machine to the point of being less than useful.
-
-
- Programming Interface
-
-
- For a C programmer, any new system must be judged in terms of its function
- library and the programmer's model that lies behind it. The Open Look
- programmer's model begins with the definitions of page, pane, and window, the
- logical elements from which a QNX Windows application is built.
- To build an application in QNX Windows, the programmer starts by defining a
- "page." At the lowest level, a page represents a buffer in memory into which
- the program builds objects that will display on the page. Each page may have
- from one to four "panes," which may be thought of as subwindows. Panes serve
- as natural subgroupings for elements on the window's picture. Tying these
- together is the visible "window," which may be thought of as a view into the
- page beneath it.
- QNX Windows is strongly object-oriented. Every element that may be put on a
- page represents a highly smart object. For example, objects such as buttons or
- menu selections remember their color, shape, and location. They also remember
- whether they highlight or change colors when selected.
- In fact, a QNX Windows object knows everything about its screen display. QNX
- Windows handles that display without needing direction from the programmer's
- application. Thus, if you click on a button, it will automatically perform
- whatever its visual task is -- become depressed, change color, or pick up a
- check mark, for example. The button will also send a message to the
- application that it has been selected.
- The underlying protocol that makes all this possible is client/server. The QNX
- Windows process runs on the display node as a windows server. (It must have a
- graphics terminal.). Client processes queue requests for windows services.
- This design husbands resources at the same time that it enhances performance.
- Only one instance of the windows code is necessary per display node, and the
- display is always painted by the local processor, even if the requesting
- process is running on a distant node.
- Performance can be further enhanced if applications programmers follow a "draw
- first, then show" discipline. Changes to the window should be written into the
- picture buffer with draw function calls until the new window is complete. Then
- the changes for the completed window are sent to the screen. This gives a
- pleasing, instantaneous effect on the video display.
-
-
- The Function Library
-
-
- QNX Windows' function library, the other half of its API, comprises
- approximately 200 function calls divided into 20 functional groups. These
- range from low-level functions that draw arcs, points, Bezier curves, and the
- like, to high-level calls that draw complete menus, meters, or slidebars in a
- single function call.
- The authors of the library avoided chaos by following a rational naming
- convention very much along modern C lines. Each function name begins with a
- functional-group word, such as Draw, Picture, Set, or Event. Following that is
- the name of the target object or action, as in DrawButton, PictureOpen,
- SetColor, or EventWaiting. As with all good naming conventions, the programmer
- is able to predict the name and existence of a function very soon after
- starting to use QNX Windows. I have put aside my grumbling about the "creeping
- Pascalization" of C, and have oiled my rusty shift key.
-
-
- Object Orientation
-
-
-
- Using the QNX Windows library is facilitated by the design paradigm that all
- screen constructs, such as buttons or menus, are high level objects that store
- a complete set of characteristics for that object. Each windows process has a
- context -- a set of color, fill, and line defaults that will apply to each
- draw operation as it occurs. These characteristics are copied through to the
- drawn object, thus fixing its characteristics even if the context is changed
- later.
- Each object, be it a menu or dialog box, has an optional "tag," a string
- identifier of up to 255 characters that may be used as a handle in subsequent
- manipulations of that object. Although objects can also be located by their
- type or their coordinates on the picture, the use of tags goes a long way
- toward preserving the sanity of the QNX Windows programmer.
-
-
- High-Level Objects
-
-
- Because the Open Look standard defines a continuum of user interfaces, the
- designers of QNX Windows had the opportunity to develop many complex objects,
- such as menus and dialogs, into single function calls. That opportunity was
- not neglected.
- Menus are a special case of dialogs. Menus follow the Open Look conventions of
- having a wedge shaped submenu mark and being selectable through the right
- mouse button. You can display a menu by calling the function Menu, which needs
- only five arguments:
- a label
- a title
- the number of display columns
- a list of menu items separated by semicolons
- a string of option characters
- You don't need to write any mouse-handling code for the menu, or indeed for
- any part of a QNX Windows application. The server handles the mouse
- consistently at all times. It returns nothing to the application but
- selections made by mouse click.
- A program frequently needs file-selection menus. A second function call,
- FileMenu, creates a menu with two special arguments -- a directory and a
- wild-card skeleton. The menu can present files of a specific type from a
- specific directory. Both arguments are string pointers, so the application can
- put the directory and skeleton under user control.
- Another standardized QNX Windows dialog is a "notice." A notice displays a
- message such as "Printer has jammed," with a clear button labeled "OK" (as if
- a jammed printer is ever OK). A help text system allows contextual help to be
- stored in simple text files and called up by an interactive browser. These
- help text files are all stored in a common directory. An application could
- even allow a user to modify the program's help text.
- QNX is so closely associated with the world of mission-critical and real-time
- systems. It is heartening to see that the QNX Windows library provides two
- functions called DrawNumber and DrawReal. These present numerical data to
- screen objects in a variety of formats, including dials, meters, bars, and
- gauges with or without sliders, end buttons, and ticks. It is a tribute to the
- server approach that these formidable coding chores can be reduced to function
- calls each with five parameters. They can also be present for all processes
- with a single instance of code in the server.
-
-
- Programming Paradigm
-
-
- A QNX Windows application does surprisingly little setup code to get the
- system rolling. Listing 1 shows the code necessary to produce a "Hello world"
- message box. This illustrates the underlying "draw, then show" philosophy in
- QNX Windows. The first three lines set up the picture in memory. The fourth
- line uses the picture tag test to show the picture in a window. Line five
- replaces the standard QNX call to receive. As a message passing OS, QNX is
- geared to using asynchronous, event-driven algorithms. No QNX code fragment
- would be complete without some line that waits on a message (or sends one).
- The code in Listing 1 cannot discriminate among messages. That is acceptable
- perhaps if we simply want to clear the "Hello world" after any mouse click.
- Usually, however, a piece of QNX Windows code will have an event loop similar
- to the one in Listing 2.
- Listing 3 is the complete code for a puzzle game shipped with QNX Windows. The
- puzzle is familiar to most of us as a small flat box of numbered tiles that
- can be slid past each other. The box has 15 tiles in a 4 X 4 enclosure,
- leaving one space for maneuvering the tiles. I don't like these puzzles,
- probably because I'm not any good at solving them, but I do note that the
- source for this windows application has only 300 lines of C code.
-
-
- Summary
-
-
- The QNX Windows GUI combines the features of both a high-level and low-level C
- windowing library with a client/server window driver design. The resulting GUI
- provides a powerful development environmen. It does not sacrifice the
- performance that has made QNX a favorite OS among those involved in
- mission-critical or real-time software.
- The QNX Windows developers toolkit can, I suspect, speed application
- development for current QNX users. Perhaps its handsome presentation,
- following the Open Look standard, will lure new QNX users into the fold.
- Figure 1
-
- Listing 1
- EVENT_MSG msg; /* allocate space for a message */
-
- Picture("test", NULL); /* create a picture to write to */
- DrawAt(200, 100); /* set the coordinates */
- DrawText("Hello, world", 0, 0, 0, NULL, NULL); /* draw it */
- Window("test", "T", "C", NULL); /* show it */
- GetEvent(0, &msg, sizeof(EVENT_MSG)); /* wait for an event */
-
-
- Listing 2
- EVENT_MSG event;
- /* allocate space for an
- event message */
- draw_screen();
- /* put up your window */
- for(;;)
- { /* receive the message
- from QNX Windows */
- GetEvent(0, &event,
- sizeof(EVENT_MSG));
- /* extract the message type
- and act appropriately */
-
- switch(Event(&event))
- {
- case CLICK:
- /* somebody clicked
- the mouse */
- do_something();
- continue;
- case QUIT:
- /* the user wants to quit */
- case TERMINATED:
- /* the server is being
- shut down */
- shutdown();
- exit(0);
- }
- }
-
-
- Listing 3
- /*
- * puzzle.c: This QNX Windows program creates a
- * moving tile puzzle, with 15 numbered tiles in a
- * 4 X 4 enclosure.
- *
- * It is reproduced here by permission of Quantum
- * Software Systems, Ltd of Kanata, Canada.
- */
-
- #include <stdio.h>
- #include <windows.h>
-
- #define RECT_HEIGHT 250
- #define RECT_WIDTH 360
- #define PANE_BORDER 140
-
- #define PANE_HEIGHT ((rows)*RECT_HEIGHT + 120)
- #define PANE_WIDTH ((rows)*RECT_WIDTH + 120)
- #define CASE(str) (((str)[0] << 8) ½ (str)[1])
-
- EVENT_MSG msg;
-
- int rows = 4, max_num;
- int b[20][20], old[20][20];
- int mv;
-
- main()
- {
- int go = YES, wid, pid;
-
- /* connect to QWindows */
- if ( !GraphicsOpen (NULL))
- exit ( -1 );
-
- SetName("Puzzle", NULL); /* Optional */
-
- pid = PictureOpen ( "pict", NULL, NULL, LIGHT_GRAY,
- NULL, NULL, NULL );
- PictureHighlightOptions( NULL, 'N', 0, 0 );
-
-
- init();
- display();
-
- wid = WindowOpen ("Puzzle", PANE_HEIGHT, PANE_WIDTH,
- "MT480-r;s", NULL, NULL, pid);
- /*
- * Put control button in top frame.
- */
-
- WindowBarCurrent( TOP, NULL );
- SetButton( NULL, WHITE, NULL );
- DrawAt( 300, 120 );
- AttachDialog("Options", NULL,
- "Scramble;Size½@(Easy½03;The Famous 4 x 4½04;Hard½
- 05;Very Hard½06;You've got to be
- Kidding½ 10)^R",
- NULL, "MD", NULL, NULL);
- DrawButton( "Options", NULL, "Ns;D", "op");
- Draw();
- WindowBarCurrent( NULL, NULL );
-
- load_icon( "/windows/apps/puzzle" );
- DrawStart(NULL, 0);
-
- while ( go )
- {
- GetEvent ( 0, &msg, sizeof(msg));
-
- switch ( Event ( &msg ) )
- {
- case QUIT:
- WindowClose( 0, NULL );
- go = NO;
- break;
-
- case TERMINATED:
- go = NO;
- break;
-
- case CLICK:
- if (msg.hdr.code == 'S') {
- /* game piece selected */
- move(atoi(msg.hdr.key) - 1);
- mv++;
- win ();
- }
-
- break;
-
- case DIALOG:
- switch(CASE(msg.hdr.key)) {
- case '03': case '04': case '05': case '06':
- case '07': case '10':
- rows = atoi(msg.hdr.key);
- case 'Sc':
- new_game();
- break;
- }
- }
-
- }
- GraphicsClose();
- exit (0);
- }
-
- init() {
- int i, j, r, loop;
-
- max_num = rows*rows;
-
- for (i = 0; i < rows; i++)
- for (j = 0; j < rows; j++) {
- b[i][j] = ( i * rows + j);
- old[i][j] = -1;
- }
-
- i = j = rows - 1;
- srand(get_ticks());
- r = rand() % 200 + 500;
-
- for (loop = 0; loop < 5000; ++loop) {
- switch (rand() % 4) {
- case 0:
- if (i != 0) {
- b[i][j] = b[i - 1][j];
- b[i - 1][j] = max_num;
- --i;
- }
- break;
-
- case 1:
- if (i != rows - 1) {
- b[i][j] = b[i + 1][j];
- b[i + 1][j] = max_num;
- i++;
- }
- break;
-
- case 2:
- if (j != 0) {
- b[i][j] = b[i][j - 1];
- b[i][j - 1] = max_num;
- --j;
- }
- break;
-
- case 3:
- if (j != rows - 1) {
- b[i] [j] = b[i] [j + 1];
- b[i] [j + 1] = max_num;
- j++;
- }
- break;
- }
- }
- }
-
- display() {
- int i, j;
-
-
- for (i = 0; i < rows; i++)
- if (memcmp(b[i], old[i], rows)) {
- for (j = 0; j < rows; j++)
- if (b[i][j] != max_num)
- draw_number(i, j, b[i][j] + 1);
- memcpy(old[i], b[i], rows);
- }
- }
-
- draw_number( row, col, number)
- int row, col, number;
- {
- char tbuf[4];
-
- tsprintf(tbuf, "%2d", number);
-
- SetColor ( "T", BLACK );
-
- DrawAt (PANE_BORDER + (row*RECT_HEIGHT) + CHARH/2,
- PANE_BORDER + (col*RECT_WIDTH ));
-
- DrawText( tbuf, 0, 0, 0, "Sem", tbuf);
-
- Draw ();
- }
-
- move_number( row, col, number)
- int row, col, number;
- {
- char buffer[7];
-
- tsprintf(buffer, "%2d", number);
- ShiftTo( buffer, PANE_BORDER + (row*RECT_HEIGHT) + CHARH/2,
- PANE_BORDER + (col*RECT_WIDTH ));
- }
-
-
- move(m)
- int m;
- {
- int i1, j1, i, j, i2, j2, c;
-
- i1 = j1 = -1;
-
- for (i = 0; i1 == -1 && i < rows; i++)
- for (j = 0; j < rows; j++)
- if (b[i][j] == m) {
- i1 = i;
- j1 = j;
- break;
- }
-
- if (i1 != -1) {
- i2 = j2 = -1;
- for (i = 0; i < rows; i++)
-
- if (b[i][j1] == max_num) {
- i2 = i;
-
- j2 = j1;
- break;
- }
-
- if (i2 == -1)
- for (j = 0; j < rows; j++)
- if (b[i1][j] == max_num) {
- i2 = i1;
- j2 = j;
- break;
- }
-
- if (i2 == -1)
- return;
- }
- else
- return;
-
- /* Hold picture for smoother updates */
- PictureHold();
-
- if (i1 == i2)
- if (j1 < j2)
- for (j = j2 - 1; j >= j1; --j) {
- b[i1][j + 1] = b[i1][j];
- move_number(i1, j+1, b[i1][j]+1);
- }
- else
- for (j = j2; j < j1; j++) {
- b[i1][j = b[i1][j + 1];
- move_number(i1, j, b[i1][j+1]+1);
- }
-
- else
- if (i1 < i2)
- for (i = i2 - 1; i >= i1; --i) {
- b[i + 1][j1] = b[i][j1];
- move_number(i+1, j1, b[i][j1]+1);
- }
- else
- for (i = i2; i < i1; i++) {
- b[i][j1] = b[i + 1][j1];
- move_number(i, j1, b[i+1][j1]+1);
- }
- b[i1][j1] = max_num;
-
- /* update current picture */
- PictureContinue();
- }
-
- win() {
- int c = 0, i, j;
- char buf[50];
-
- for (i = 0; i < rows; i++)
- for (j = 0; j < rows; j++)
- if (b[i][j] < c)
- return( 0 );
- else
-
- c = (int) b[i][j];
-
- tsprintf(buf, "You got it in %d moves!", mv);
- Notice( NULL, "You Win", NULL, "W", buf);
-
- return( 1 );
- }
-
- new_game() {
- RECT_AREA area;
-
- WindowInfo(NULL,&area,NULL,NULL,NULL,NULL);
-
- /* erase all elements in the picture */
- WindowHold();
- Erase( ALL );
-
- area.height = PANE_HEIGHT;
- area.width = PANE_WIDTH;
-
- init();
- display();
- mv = 0;
-
- WindowChange( &area, NULL, KEEP, NULL, "!" );
- WindowContinue();
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Of Mice And Menus
-
-
- Keith Bugg
-
-
- Keith Bugg is currently a senior software engineer with Analysas Corp. His ten
- years experience as a programmer/analyst includes five years in C, all on the
- DEC VAX or the PC. Readers may contact him at 122 E. Morningside Drive, Oak
- Ridge, TN 37830, (615) 482-9515.
-
-
- Including a mouse in your application enhances its ease of use and
- friendliness. Novice users can find the large keyboard intimidating. Using a
- mouse to navigate a program can reduce this apprehension. Also, using a mouse
- lowers the learning curve -- the simplicity of "point and click" reinforces
- the user's intuition.
- The programmer benefits from the improved data integrity a mouse supplies. In
- a classic menu-driven system, the program must read the user's keystroke(s),
- validate results, and report on them. In a mouse-driven system, the user can
- only select a valid option. If the user clicks the mouse on a vacant area of
- the screen, the program simply ignores the clicks.
- I describe here the design and implementation of a C program that allows a
- mouse to select options in a menu-driven application. The sample program
- demonstrates many of the technical issues, and can serve as a template in your
- collection of tools. I developed the code for an IBM PC running MS-DOS. The
- program requires a Microsoft mouse (or compatible), its associated driver, and
- the Microsoft mouse library, MOUSE.LIB. I used the Microsoft C compiler, but
- the code should work with other compilers with a minimum of changes. Whatever
- compiler you choose, you must buy MOUSE.LIB. It is available from Microsoft as
- part of the Microsoft Mouse Programmer's Guide, and costs about $25.
- A program using the Microsoft mouse can be compiled in any of the memory
- models, and then linked with MOUSE.LIB, as in this example:
- cl/AM /c myprogram.c
- (creates a medium model but does not link)
- link myprogram.obj,MOUSE.LIB
- (creates executeable of 'myprogram')
- The Microsoft compiler switch /AM determines the memory model, in this case
- medium. Use /AS for small, /AC for compact, and /AL for large and huge.
- The mouse library contains all the routines needed to manipulate the mouse.
- Utilities include reading the cursor, writing the cursor, and determining the
- status of mouse buttons. The Microsoft mouse performs 23 functions.
- The call to mouse uses four parameters which are declared as integers and
- passed by reference, as in mouse (&m1, &m2, &m3, &m4). (Each argument of type
- pointer to int.) The first parameter indicates the command to be performed.
- The others provide additional information, such as cursor coordinates or
- button selection. Table 1 summarizes the calls most widely used.
- Mouse coordinates differ from screen coordinates by a factor of eight when in
- text mode, because each character is an 8-by-8 pixel group. The upper
- left-hand corner of the screen is the origin. It has coordinates (0, 0). The
- lower right-hand corner of the screen has coordinates (632, 192), assuming the
- usual 25 lines of 80 characters each.
- BIOS calls solve most of the technical design issues that affect including a
- mouse in a program. A mouse-driven program should begin by checking the target
- platform for the existence of a mouse and its driver. Your program can then
- adapt automatically to machines without a mouse. Incidentally, you might want
- menu options to be selected via the keyboard whether your target platform has
- a mouse or not. The sample program does not include this mode of operation,
- but you can easily include it. Programming for the arrow keys, the function
- keys, and other special keys (such as Home and End) is fairly straightforward.
-
- The next issue concerns manipulating the cursors. You must control both the
- default text cursor and the mouse cursor. Use BIOS calls to control the text
- cursor. You can also use BIOS calls to access the video display. The sample
- program displays the example menu using BIOS calls. Use an array of structures
- to define the menu. Each element holds the text of the menu option plus the
- screen and mouse coordinates. With this approach, you can make changes easily.
- When using a mouse in your applications, you should disable the control-break
- interrupt, and restore it upon termination. If you don't, and the user types a
- control-break, the program could return control to DOS with the text cursor
- off and the mouse cursor still on. This situation could confuse and frustrate
- the user. Remember that an important reason to using a mouse is to create a
- level of comfort and to reduce complexity. You access the control break
- interrupt by calling signal. See the Microsoft C documentation for a
- description of this function.
- Although it is beyond the scope of this article, bear in mind that a
- well-designed application would save and restore the target platform's
- configuration. This includes the graphics mode and screen color, for example.
- The focus of this article is more on the fundamental requirements. At that
- level, little difference exists between a mouse-driven program and a
- traditional program. Both give the user information, wait for a response, and
- take action.
- The sample program executes an infinite loop waiting for an event to occur. It
- ignores meaningless events such as clicking the mouse button on a vacant area
- of the menu. It processes valid ones such as backlighting a menu option when
- the mouse cursor is moved into it. Knowing the mouse's characteristics is
- essential for organizing the logic flow of an application.
- Interfacing a mouse to an application brings a versatile and elegant dimension
- to the user-interface. All well-engineered software emphasizes what to do over
- how to do it. Mouse-driven programs are no exception. You can use the sample
- program as a template for other programs. Here are some suggestions: a pop-up
- calculator, a control panel in a process-control environment, a cut-and-paste
- buffer for a text editor, or a help screen with hypertext capabilities.
- References
- Microsoft Mouse Programmer's Reference Guide by Microsoft Press.
- C Power Users Guide by Herb Schildt
- Table 1
- m1 Description
- ------------------------------------------------------------------------------
- 0 Resets mouse, number of buttons returned in m2; status returned in m1
- (0 indicates mouse not installed, -1 if OK)
-
- 1 Enable mouse cursor
-
- 2 Disable mouse cursor
-
- 3 Returns button status and cursor coordinates (m3 = horizontal,
- m4 = vertical
-
- 4 Set mouse cursor (m3= horizontal, m4 = vertical) screen address
-
- 5 Returns button press information (set m2 = 0 for left button, 1 for right.
- Number of presses are returned in m2; m3 and m4 have right cursor
- position)
-
- 6 Same as (5), but gets information when button is RELEASED
-
- 7 Limits range of cursor's horizontal movement
-
- 8 Limits range of cursor's vertical movement
-
- Listing 1 (mouse.c)
- /*
-
- NAME = MOUSE.C
- This program demonstrates the fundamentals of using a mouse to
- select options in a menu-driven application. Uses two "dummy"
- functions: genledg() and payroll().
- Note: Function chkdrv() taken from page 7-16 of "Microsoft
- Mouse Programmer's Reference Guide"
- */
-
- #include <stdio.h>
- #include <dos.h> /* required for BIOS calls, etc. */
- #include <string.h>
- #include <signal.h> /* disable & enable control break */
-
- #define RED_BACK 74 /* display using a red background */
-
- extern void pascal far mouse(int*, int*, int*, int*); /*declare mouse*/
-
- void txtcursor(char cur), cls(), outstr(), genledg(), payroll(), heading();
- void chkdrv(); /* checks for existence of mouse driver */
-
- int i, handler(); /* handler() part of control-break */
- int m1, m2, m3, m4; /* mouse parameters */
- struct menu /* structure for our sample menu */
- {
-
- char text[20]; /* text of options */
- int x; /* horiz. screen coordinate */
- int y; /* vert. screen coordinate */
- int mh,mh2,mv; /* mouse horiz/vert. coordinates*/
- }options[3]; /* 3 options in our sample prog.*/
-
- main()
- {
-
- int choice, loop;
-
- /* disable Control-Break interrupt */
-
- if(signal(SIGINT,handler) == (int(*) ())-1)
- {
- cls();
- printf("Fatal error - could not disable Control-Break\n");
- abort();
- }
-
- /* check for mouse hardware & driver */
- m1=O;
- mouse(&m1,&m2,&m3,&m4); /* check hardware here */
- if(m1 != -1)
- {
-
- cls();
- printf("Fatal error - no mouse found..\n");
- abort();
- }
- chkdrv(); /* check for mouse driver */
-
- /* "Populate" our structure with data */
-
-
- strcpy(options[0].text, "General Ledger" );
- options[0].x = 6;
- options[0].y = 30;
- options[0].mh = 240; /* mh & mh2 define the horiz. */
- options[0].mh2= 344; /* limits of option's "hot spot"*/
- options[0].mv = 48; /* define vertical limit here */
-
- strcpy(options[1].text, "Payroll" );
- options[1].x = 8;
- options[1].y = 30;
- options[1].mh = 240;
- options[1].mh2 = 288;
- options[1].mv = 64;
-
- strcpy(options[2].text,"Exit");
- options[2].x = 10;
- options[2].y = 30;
- options[2].mh = 240;
- options[2].mh2= 264;
- options[2].mv = 80;
-
- heading(); /* display menu, etc. */
- m1=m2=m3=m4=i=0; /* initialize variables */
- mouse(&m1,&m2,&m3,&m4); /* init mouse */
- txtcursor(0); /* turn off text cursor */
- m1=1; /* show cursor */
- mouse(&m1,&m2,&m3,&m4);
-
- /* now set up infinite loop which processes the
- user's requests */
- loop=1;
- while(loop)
- {
-
- m1=3; /* read mouse cursor position */
- m2=m3=m4=0;
- mouse(&m1,&m2,&m3,&m4);
-
- for(i=0; i < 3; ++i) /* is cursor in 1 of 3 options? */
- {
- if((m3 >=options[i].mh && m3 <= options[i].mh2) &&
- (m4 == options[i].mv))
- {
- outstr(options[i].x,options[i].y, options[i].text,RED_BACK);
- break;
- }
- else
- outstr(options[i].x,options[i].y, options[i].text,10);
- }
- m1=6; /* read left button */
- m2=m3=m4=0;
- mouse(&m1,&m2,&m3,&m4);
- if(m2) /* user pressed left button */
- {
- choice= -1; /* reset from last choice made */
- for(i=0; i < 3; i++)
- {
- if((m3 >= options[i].mh && m3 <= options[i].mh2)
- && (m4 == options[i].mv))
-
- choice = i;
- }
- switch(choice)
- {
- case 0: /* selected GEN. LEDGER */
- genledg();
- break;
- case 1: /* selected PAYROLL */
- payroll();
- break;
- case 2: /* selected EXIT */
- loop=0; /* ends infinite loop */
- break;
- default: /* meaningless "click" */
- break;
- }
- m1=m2=m3=m4=i=0;
- mouse(&m1,&m2,&n3,&m4); /* init mouse */
- m1=1; /* show cursor */
- mouse (&m1,&m2,&m3,&m4);
- }
- }
- /* perform housekeeping chores before exiting */
-
- m1=2; /* hide mouse cursor */
- mouse(&m1,&m2,&m3,&m4);
- txtcursor(1); /* restore text cursor */
- cls(); /* clear screen */
- signal(SIGINT, SIG_IGN); /* enable control-break */
- exit(0); /* return to DOS */
- } /* END MAIN PROGRAM */
-
- void txtcursor(char cur) /* controls text cursor */
- {
- union REGS r; /* prepare registers,etc */
- r.h.ah = 1;
- if(cur) /* turn text cursor on */
-
- {
- r.h.cl = 7;
- r.h.ch = 6;
- }
- else
- {
- r.h.ch = 113; /* set bits 5,6 */
- r.h.cl = 0; /* set lower bits */
- }
- int86(0x10, &r, &r); /* execute the interrupt */
-
- }
-
- void cls() /* clears the screen */
- {
- union REGS r;
- r.h.ah = 6; /* screen scroll code */
- r.h.al = 0; /* clear screen code */
- r.h.ch = 0; /* start row */
- r.h.cl = 0; /* start column */
- r.h.dh = 24; /* end row */
-
- r.h.dl = 79; /* end column */
- r.h.bh = 7;
- int86(0x10, &r, &r); /* execute interrupt */
- }
- void outstr(x,y,s, color) /* writes a string to video RAM */
- char *s; /* string to write */
- char color; /* color of string */
- int x,y; /* where to start writing*/
- {
- char far *vid_mem; /* video memory pointer */
- vid_mem= (char far *) 0xB8000000; /* start of video memory */
- vid_mem=vid_mem+((x * 160) + (y * 2)); /* where to start output */
- while(*s) /* loop thru string... */
- {
-
- *vid_mem=*s++; /* move string into video memory*/
- ++vid_mem; /* incr. to next byte=attribute */
- *vid_mem=color; /* and set the color there */
- ++vid_mem; /* incr. for next character in */
- } /* the string "s" .... */
- }
-
- void chkdrv() /* checks for presence of mouse driver */
- {
- union REGS inregs, outregs;
- struct SREGS segregs;
- long address;
- unsigned char first_byte;
-
- inregs.x.ax = 0x3533;
- intdosx(&inregs, &outregs, &segregs);
-
- address=(((long) segregs.es) << 16)+(long) outregs.x.bx;
- first_byte= * (long far *) address;
-
- if((address==0) ½½ (first_byte==0xcf))
- {
- cls();
- printf("System has a mouse, but no mouse driver !\n");
- printf("Install file MOUSE.SYS and re-start this program.");
- exit(0);
- }
- }
- void genledg() /* "dummy" function */
- {
- m1=2; /* hide cursor */
- mouse(&m1,&m2,&m3,&m4);
- cls();
- printf("Perform Option 1, 'General Ledger'\nNow press 'ENTER'...");
- getchar();
- heading();
- }
-
- void payroll() /* "dummy" function */
-
- {
- m1=2; /* hide cursor */
- mouse(&m1,&m2,&m3,&m4);
- cls();
-
- printf("Perform Option 2, 'Payroll'\nNow press 'ENTER'...");
- getchar();
- heading();
- }
- void heading() /* menu heading */
- {
- cls(); /* clear screen */
- outstr(3,25,"Sample Mouse/Menu Program",14);
- for(i=0; i < 3; i++)
- outstr(options[i].x,options[i].y,options[i].text,10);
- }
-
- int handler() /* control-break function */
- {
- signal(SIGINT, SIG_IGN);
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Point-And-Shoot Menus
-
-
- John Matsche
-
-
- John J. Matsche has a B.S and an M.B.A. from the University of Central
- Florida. His experience includes ten years of systems analysis on various
- platforms. His language experience includes C++, Pascual, Assembler, Algol,
- Cobol, and Basic. You can contact John at 4662 Millhaven Rd., Martinez, GA
- 30907.
-
-
- In the beginning, programming a menu screen was easy. You presented a list of
- program options to the user. The user would select an option by entering the
- corresponding number and pressing the enter key. It was a simple programming
- task, usually requiring nothing more than a looping series of write statements
- followed by a read, then some sort of a branching statement.
- When I bought my first copy of Lotus 1-2-3, I realized the inadequacies of
- this algorithm. The point-and-shoot menus that run along the top of my
- spreadsheet do more than provide a raw list of program options. These menus
- also describe each option. Once selected, it is easy to maneuver down the
- hierarchy, and even easier to back out. In short, the point-and-shoot menu
- system is more intuitive. More important, at that stage in my career, the
- point and shoot system looked more polished and professional.
- The Lotus menu system has many variations. Basically, it consists of a list of
- words or short narrative that describes an option. The selected option, is
- highlighted in some fashion, usually by display in reverse video. When the
- user presses the right arrow key, the next option becomes the selected option.
- The left arrow key selects the previous option. The user points to an option
- by pressing the arrow keys, then invokes the option by pressing the enter key
- -- hence the name "point and shoot." If the user presses the escape key, the
- menu backs out. No option is selected and control returns to some previous
- state.
- I set out to develop an algorithm that would implement point-and-shoot menus
- as efficiently as possible. At the same time, I wanted it to be generic enough
- to work with any application program. C is an ideal programming language for
- this type of project. It is low-level enough to provide the speed necessary
- for quick screen displays. It is compact enough to have negligible impact on
- an application programs's size. For this project, I used Borland's Turbo C++
- compiler running on an IBM-compatible 386 using MS-DOS v4.01.
-
-
- Implementation
-
-
- I make use of three video modes that I call normal, reverse, and highlight.
- These terms are left over from the days of monochrome displays, but are also
- applicable to color systems. Simply choose your own favorite color scheme for
- each mode. In this implementation, reverse video implies that both background
- and foreground colors are reversed from normal. Highlight video uses the
- intense foreground color of the normal video mode.
- The program shown in Listing 1 contains the complete implementation. I added a
- main function to demonstrate the execution of the menu system. By removing
- main, you make the program a compilable module that can easily be linked into
- any application program. To complete the conversion from program to module,
- you should create a separate header file containing a prototype of the menu
- function. The remaining functions should stay relatively hidden from the
- application.
- The #defines at the top of the listing provide logical names for the various
- keys that menu recognizes. Some of the keys can be read directly by the getch
- function. For example, the enter key will always generate an ASCII 13 (hex
- 0x0d). Other keys, such as the function keys and the arrows, do not generate a
- single character code when pressed. To recognize these keys, you must look at
- something called a scan code.
- From a software point of view, think of a scan code as an extended return
- value for special keys. Whenever a special key such as a function or arrow key
- is pressed, getch returns an ASCII NUL (hex 0x00). The next call to getch
- returns the scan code for the pressed key. Note that this does not mean you
- must press the special key twice. It simply means that whenever you press a
- special key two characters are loaded into the keyboard buffer instead of just
- one. In fact, if you were to use getch as a pause mechanism, and you didn't
- test for scan codes, you might inadvertently pick up the scan code later on in
- another getch call.
- As previously mentioned, main is a demonstration function that is not a part
- of the menu logic. It illustrates how easy the menu is to call. By looking at
- main, you see that menu requires only two parameters. The first points to the
- first element of an array of pointers to the menu items to be displayed. Note
- that in this implementation, each menu item begins with a different letter.
- This restriction allows you to select a menu item by pressing this letter on
- the keyboard. The array is terminated with a null pointer. You don't need to
- tell menu how many items are being passed to it. The second parameter to menu
- is a Boolean value (1 means true, 0 means false) that specifies whether or not
- a horizontal format is to be used.
- The rest of the setup sets the desired colors and formats the screen. The
- primary loop in main displays which item the user chose and positions the
- cursor for the next menu display. The loop terminates when the user presses Q
- for quit.
- Five functions carry out the menu's actual implementation: show, findletter,
- init, keypress, and menu. show displays one menu item on the screen.
- findletter searches the list of items for the letter pressed by the user. init
- sets up screen locations for displaying items, keypress handles all keyboard
- input, and menu controls the show.
- menu initializes screen colors and locations, displays all of the items, gets
- user input, and loops until the user wants to exit. To initialize screen
- locations, call the function init. The gettextinfo function, included with
- Turbo C++, is called to provide menu with screen information as stored in the
- text_info type structure called info. This structure allows you to derive the
- current colors and cursor position for reference. Next, menu turns off the
- cursor to present a cleaner looking menu and calls show repeatedly to display
- each menu item.
- The main loop of menu displays the currently selected menu item in reverse
- video, then waits for the user to press a key. Once a key is pressed, the
- function tests for a scan code. menu then displays the current item in normal
- video, effectively de-selecting it from the user's point of view. Finally, the
- function calls keypress to act on the selected key. The value returned by
- keypress determines whether or not the loop is repeated.
- The last task menu must complete before exiting is to turn the cursor back on
- and return the user's selection to the calling function.
- Screen locations, as stated earlier, are set up by init and used by show.
- Setting up the locations depends on which format, vertical or horizontal, is
- specified. For vertical formats, screen location is simply based on the
- starting row plus the current index. For horizontal formats, the position is
- calculated from the starting column location plus the length of the previous
- menu items. Because the horizontal format involves more calculation, it is
- only done once in init, then it is stored in an array.
- show adds the starting row and column information, contained in the x and y
- variables, to an offset based on the chosen format (horizontal or vertical).
- It sets the video mode to highlight by setting the high bit of the foreground
- color attribute. It then displays the first character, returns video mode to
- normal, and displays the rest of the menu item.
- keypress has been set up separately to isolate the logic necessary to handle
- the user's input, so you can easily add new key handling ability to the
- function's repertoire. keypress is responsible for calculating which menu item
- will be made current based on which key the user pressed. It does this using
- an index value (variable i) that can vary from zero to the maximum number of
- items less one. The function returns --1 as an index value when the user has
- pressed the enter key. menu then knows to quit and return whichever option was
- previously current to the calling function. Note that keypress returns an
- index value instead of the menu item letter. keypress was implemented to
- return an index value so show will easily know where to display the next menu
- item.
- The last function to discuss is findletter. It is called from keypress
- whenever the user presses a key that is not specifically handled. findletter
- loops through the menu items looking for a match between the key pressed and
- the first character of the menu item. If found, it changes the current menu
- item index and passes it to the caller as a return value.
- Collectively, these functions provide a simple and clean looking
- point-and-shoot menu system that is flexible enough to handle most
- applications' needs. At the same time, it is simple enough not to bog down the
- programmer in detail. There are, obviously, many enhancements that can be made
- to this system.
-
-
- Enhancements
-
-
- The basic point-and-shoot menu theme has many variations. For example, you can
- display a more descriptive explanation of each menu item on a separate line
- when it is selected. To do this you might want to pass to menu another array
- of pointers (call this the description array) which correspond on a
- one-for-one basis to the original array (list of menu items). If the first
- menu item is "Load," then the first element of the description array might be
- "Load a file from disk." show would display this description on some
- predesignated line. Many applications reserve the bottom line of the screen
- display for this purpose.
- You could enhance this implementation by creating a more sophisticated
- approach to color selection. Even though the menu example described in this
- article is a stand-alone module, it would normally be incorporated as part of
- an overall module to handle screen formatting. Several other utility functions
- for windowing and formatting would be included that also require a way to
- specify color selection. One popular way to designate color selection is to
- define a color palette, implemented as an array of allowable color
- combinations for foreground and background. You might select normal,
- highlight, and reverse colors with a call like color(3,2,5) in which each
- parameter represents a specific index within the color palette.
- This particular implementation of point-and-shoot menus relies on the user to
- provide a Quit option as one of the menu items. Many applications recognize
- the escape key as the key to press to back out of a menu without selecting
- anything. To add this capability to the example, modify keypress to return a
- special value for escape to menu. A value of --2 should be used since --1
- designates the enter key. (Remember that keypress returns an index value, not
- a character.) In turn, menu returns some prearranged value such as ASCII 27,
- so the calling application can recognize a back-out request and act
- accordingly.
- Many applications these days are demanding mouse support. The actual
- implementation would depend on the mouse support library that you use. A
- detailed discussion of mouse support is beyond the scope of this article, but
- its impact on the menu system is easy to describe.
- Adding mouse support would involve replacing the getch logic used for
- detecting characters and scan codes with a separate function. This function
- would return the same character value or scan code based on the user's
- selection. The function would probably detect what the user selected using
- some sort of loop that continuously tests the keyboard and mouse devices for
- input. You need modify no other function.
- This example of point-and-shoot menus was implemented in ANSI C to enhance its
- portability. Note that it's not strictly ANSI. For example, gettextinfo is
- available only for IBM-compatible systems. However, other systems do have
- other functions for performing similar tasks.
- Implementing a point-and-shoot menu system with the object-oriented flavor of
- Turbo C++ has many advantages also. One of the greatest advantages is the
- ability of a function to inherit behavior. For example, if you want to enhance
- the behavior of function keypress (called method keypress in OOP) to recognize
- the escape key, you create a new function (method) that contains only the
- escape key handling logic. Everything else is already provided.
-
-
- Summary
-
-
- You can approach programming problems or ideas in a variety of ways. I've
- attempted to describe one generalized approach and several possible
- enhancements that I've had great success with in my own application programs.
- The overall design goal of this menu system is to isolate into separate
- functions, as much as possible, discreet parts of the process such as keyboard
- handling, location calculations, and screen displays. This modular approach
- facilitates making future enhancements without completely rewriting the
- process every time.
-
- Listing 1 (cujmenu.c)
- /************************************************************
- An implementation of the point and shoot menu system.
- The main function is a simple demonstration routine.
-
- */
-
- #include <stdio.h>
- #include <conio.h>
-
- #define CRET 0x0d /* key definitions */
- #define TAB 0x09
- #define BACKTAB 0x0f /* key scan codes */
- #define UPARROW 0x48
- #define DNARROW 0x50
- #define LTARROW 0x4b
- #define RTARROW 0x4d
- #define PAGEUP 0x49
- #define PAGEDN 0x51
- #define HOMEKEY 0x47
- #define ENDKEY 0x4f
- #define CTRLEND 0x75
-
- /************************************************************
- display one line of menu at the proper location. If
- an X offset is specified then this must be horizontal
- formatted menu.
- */
- void show (int x,int y,int xoff,int yoff,char *p,int fore)
- {
- if (xoff) /* if an X offset exist */
- x += xoff;
- else
- y += yoff;
-
- gotoxy(x,y);
- textcolor(fore8); /* use intense color */
- putch(*p++); /* print the first char */
- textcolor(fore); /* back to normal color */
- cputs(p); /* print the rest of it */
- }
-
- /************************************************************
- search the p list for a beginning character in the
- menu list that matches c.
- */
- int findletter (char c, char *p[], int i)
- {
- int j = 0;
-
- while (*p[j] != NULL) /* while not at end */
- if (c == *p[j]) /* if we find a match */
- { i = j; break; } /* record and quit */
- else
- j++;
-
- return(i); /* return the new idx */
- }
-
- /************************************************************
- initialize certain location variables to allow us
- to write at proper screen location.
- */
- int init (char *p[], int horz, int *xoff)
-
- {
- int max = 0, x = 0;
-
- if (horz) /* if horizontal format */
- {
- while (p[max] != NULL) /* while not at end */
- {
- *xoff++ = x; /* calc X offsets */
- x += strlen(p[max++]); /* add length of item */
- }
- }
-
- else /* else zero it out */
- {
- while (p[max++] != NULL) *xoff++ = 0;
- max--;
- }
-
- return(max); /* number of items found */
- }
-
- /************************************************************
- set the index according to user's response. returns
- the new index value or -1 if the user pressed enter.
- */
- int keypress (char *p[], char c, int i, int max)
- {
- int done = 0;
-
- switch(c) /* switch on user input */
- {
- case UPARROW:
- case LTARROW:
- case BACKTAB: i--; break;
- case DNARROW:
- case RTARROW:
- case TAB: i++; break;
- case CRET: done=1; break;
- default: i = findletter(c,p,i);
- }
-
- if (done) i = -1; else /* if done, ret -1 */
- if (i < 0) i = max-1; else /* else do range check */
- if (i == max) i = 0;
-
- return(i); /* ret new index */
- }
-
- /************************************************************
- display a point and shoot menu. Return the first
- character of the menu option selected.
- */
- char menu (char *p[], int horz)
- #define MAX_ELEMENTS 12
- {
- struct text_info info; /* holds text screen info */
- int i, j, x = 0, y, fore, back, max, savei;
- int xoff[MAX_ELEMENTS]; /* for use w/ horiz format */
- char c;
-
-
- max = init(p,horz,xoff); /* calc screen positions */
- gettextinfo(&info); /* get current screen attr */
- fore = info.attribute & 0x0f;/* extract foreground color */
- back = info.attribute >> 4; /* extract background color */
- x = info.curx; /* establish reference location */
- y = info.cury;
- _setcursortype(_NOCURSOR); /* turn cursor off */
- for (i=0; i<max; i++) /* display all menu items */
- show(x,y,xoff[i],i,p[i],fore);
- i = 0;
-
- do
- {
- textcolor(back); /* set reverse video mode */
- textbackground(fore);
- show(x,y,xoff[i],i,p[i],back); /* display selected item*/
- if ((c = toupper(getch())) == 0)/* if 0 is returned */
- c = getch(); /* then get scan code */
- textcolor(fore); /* set normal video mode */
- textbackground(back);
- show(x,y,xoff[i],i,p[i],fore); /* redisplay item */
- savei = i; /* save prev sel item */
- }
-
- while ((i = keypress(p,c,i,max)) != -1); /* quit when user selects */
-
- _setcursortype(_NORMALCURSOR); /* turn cursor back on */
- return(*p[savei]); /* return selection */
- }
-
- /************************************************************
- demonstration main function to show use of the point
- and shoot menu.
- */
- void main (void)
- {
- char *p[] = {"First ","Second ","Third",
- "Quit ",NULL}, c;
- int format = 0;
-
- textcolor(LIGHTGRAY);
- textbackground(BLUE);
- clrscr();
- cputs("Display menu in horizontal format? (y/n) >");
- if (toupper(getch()) == 'Y')format = 1;
- gotoxy(10,10);
- while ((c = menu(p,format)) != 'Q')
- {
- gotoxy(10,20);
- cprintf("The selected option is: %c\n\r",c);
- gotoxy(10,10);
- }
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Serial Communications With Turbo C
-
-
- Greg Chursenoff
-
-
- This article is not available in electronic form.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Some Small C++ Classes
-
-
- Joe Schell
-
-
- Joe Schell received his B.A. in mathematics from the University of Colorado.
- He has been programming 15 years as a hobby, five of those years in C. Joe
- specializes in writing C/C+ + libraries. You can contact him at P.O. Box 7039,
- Boulder, CO 80306.
-
-
- I have read a number of books and articles on C++. All of them have code in
- them, but none of them seem to have any small classes that are useful. I
- define a small class as one that is solely contained in the header file.
- Additionally the methods (the functions of the class) should be simple and
- will probably compile as in-line code.
- Here are a few small classes that should be useful. I will describe four
- classes: boolean, byte, word, and check_heap. The first handles true and false
- values. byte and word handle 8-bit and 16-bit values that are found in memory.
- check_heap is a debugging tool that determines if constructors and destructors
- are handling memory allocation correctly.
-
-
- boolean Class
-
-
- I modeled the boolean class on the boolean type in Pascal. The implementation
- was easy, and the code should be mostly self-explanatory. I did have some
- doubts over some implementation details. Initially I was going to make the ++
- force the boolean to true and the -- -- operator force it to false. But that
- didn't add anything since the programmer could simply set the Boolean value if
- he wants to force it to a specific value. So, instead these two operators
- reverse the value of the Boolean.
- In Pascal, the true and false values are constant. The const keyword does the
- same thing for the C+ + values. The only methods that can be called for a
- const boolean are operator int( ), operator ~( ), and make_string. Those
- methods are defined as constant by the use of const in front of the function
- definition and because they don't modify the Boolean value.
- Initially, the make_string method was actually implemented as the conversion
- operator for char*: operator char*( ). Although this method seems intuitive,
- C++ v2.0 does not have a precedence order for picking an int conversion over a
- char* conversion in conditional statements.
- Most of my code in the last two years has used Booleans in conditionals --
- although I have never printed true/false. So the choice of the conversion
- operator was easy. It is interesting to look at the line false = true; in
- testbool.cpp. Because true is a constant, that line should produce an error
- message. In Turbo C++, it produces a warning message. A similar line using a
- const int does produce an error message. The code for the boolean class is in
- boolean.hpp. The test routine testbool.cpp will check it.
-
-
- check_heap Class
-
-
- I developed the next class while I was working on some constructors and
- destructors that allocated and deallocated memory. I needed to test that the
- heap was being handled correctly after the destructors were called. Because I
- had several different simultaneous tests, a class was the perfect way to
- handle it. Look at the file testheap.cpp for several examples
- The check_heap variable is declared at the beginning of a suspected problem
- area. The method test is called any number of times after that. test will send
- an error message to cerr if the heap is different than when an instance of the
- class is declared. I found this useful even in places where I knew that the
- heap was supposed to be different. When using this class, keep in mind that it
- is often useful to pass a class instance to a function as the following line
- does in testheap.cpp.
- test_value(t);
- Don't forget that main creates a temporary value for t. It is this temporary
- value that actually gets passed to test_value. Because the destructor for the
- temporary doesn't get called until the end of main, an error message is
- produced. (Even after two years of using C++ and the check_heap class, I still
- forget this detail all the time.) The best way to avoid the problem is to
- insert another function call level into the code. You can use test_test_value.
- The temporary value is created inside this function and destroyed before it
- returns.
- If there is a problem when test is called, the function will print out the
- difference from when it started and the current position. I seldom find that
- number of any real use, but it has helped me occasionally when I had no idea
- where the problem was. I just start poking at random items until the
- difference changes by a little bit. Then I know I am in the right area.
- Since that difference can be useful, I spent a lot of time making sure that
- the number it printed made some kind of sense. PC compatibles use a segmented
- memory structure, so the code must adjust for different memory models. The
- macro CHECK_HEAP_diff_ makes this adjustment. It is possible that test will
- not be inlined even though it looks as though it should be. However, it is
- certainly not worth the effort of keeping it in a separate source file. I
- prefer to keep it all in the header file. The code for check_heap is in
- chkheap.hpp.
-
-
- byte And word Classes
-
-
- The last two classes are byte and word. These classes could almost be
- implemented as typedefs, except that they check for valid values. byte is used
- just like an unsigned char.word is used just like an unsigned int. If a larger
- value is assigned to either one, then an error message is generated at
- runtime. In the test code testbyte.cpp, byte is used like a pointer to char,
- and word is used like a pointer to int. For example:
- d = (byte*)test_byte;
- ...
- z = (word*)(&test_word);
- This is permissible only when the byte class is the same size as a char and
- the word class is the same size as an int. In particular, the byte class will
- not work on a 286 or 386 when word alignment is used. To check the size I have
- included the two #if directives that are at the end of the byte.hpp file. They
- will produce a compile-time error if the sizes do not match. I would probably
- not use these as pointers to ints or chars though. They are intended to point
- to various locations directly in memory, as when you access data for the
- serial ports or the keyboard directly. Note that on PC compatibles, these can
- also be used as far pointers in the following manner:
- byte far *b =
- far_address_of_byte;
-
-
- Some Final Comments
-
-
- I find the file form.h to be useful. It does not contain any classes, and it
- is only needed for the function form. I haven't found any way to use the Turbo
- C++ form function and still use iostream.h, so I made my own function.
- I use the following naming scheme for my files. The extension hpp is used for
- include files which have classes in them. The extensions h and hh are used as
- they are in C. The extension cpp indicates a C++ source file, containing
- either classes or final applications such as the testing routines. The c
- extension is used for C code only. I make this distinction because I write in
- C and C++, and I need to keep the two separate. In addition, when I am looking
- through code I want to be able to distinguish between include files with
- classes and those without classes. These distinctions make life a little
- easier for me.
-
- Listing 1 (boolean.hpp)
- /*********************************************************************/
- /* Boolean class. Copyright by Joe Schell 1989. */
- /*********************************************************************/
-
- #ifndef CLASS_boolean
-
- #define CLASS_boolean
-
- const int TRUE = 1;
- const int FALSE = 0;
-
- class boolean
- {
- int b; // boolean type.
- void value(const int i) { b = (i) ? TRUE : FALSE; }
-
- public:
- boolean() { b = FALSE; }
- boolean(const int i) { vaLue(i); }
- boolean(const boolean &i) { b = i.b; }
-
- operator int() const { return b; }
- operator ~() const { return b ? FALSE : TRUE; }
- boolean &operator=(const int &i) { value(i); return *this; }
- boolean &operator++() { b = b ? FALSE : TRUE; return *this; }
- boolean &operator--() { return (*this)++; }
-
- char *make_string() const { return b ? "true" : "false"; }
-
- }; // End of boolean class.
-
- const boolean true(TRUE), false(FALSE);
-
- #endif
-
-
- Listing 2 (testbool.cpp)
- /********************************************************************/
- /* Test the boolean class. */
- /********************************************************************/
-
- #include <iostream.h>
- #include <boolean.hpp>
-
- char *test(int i) {return i ? "okay.\n" : "not okay.\n"; }
-
- main()
- {
-
- boolean b1,b2;
-
- cout << "Testing boolean class\n";
- cout << "Constructed value and int() is " << test(b1 == FALSE);
- cout << "Comparison is " << test(b1 == b2);
- b1 = 1;
- cout << "Operator() is " << test(b1 == true);
- b1 = ~b2;
- cout << "Operator~() is " << test(b1 == true);
- b1++;
- cout << "Operator++() is " << test(b1 == false);
- b1--;
- cout << "Operator--() is " << test(b1 == true);
- cout << "Make_string() is " << bl.make_string() << "ly okay.\n";
-
- true = false; // This only produces warning message.
-
- }
-
-
- Listing 3 (testheap.cpp)
- /********************************************************************/
- /* Test the cheak_heap class. */
- /********************************************************************/
-
- #include <iostream.h>
- #include <chkheap.hpp>
-
- struct test_class_bad // Class that does not deallocate.
- {
- char *p;
- test_class_bad() { p = new char; }
- ~test_class_bad() { /* p is not deleted. */ }
- private:
- test_class_bad(test_class_bad&);
- };
-
- struct test_class_good // Class that does deallocate.
- {
- char *p;
- test_class_good() { p = new char; }
- test_class_good(test_class_good &t) { p = new char; *p = *(t.p); }
- ~test_class_good() { delete p; }
- };
-
- char *test_easy(const int); // Prototypes for test functions.
- char *test_class(const int);
- void test_value(test_class_good);
- void test_test_value(test_class_good &t) { test_value(t); }
-
- main()
- {
- test_class_good t;
- char *p;
-
- cout << "Testing check_heap class. Should have three okay errors\n";
- check_heap check;
-
- p = test_easy(1);
- check.test("Test_easy(1) error: ");
-
- p = test_easy(0);
- check.test("This error is okay: ");
- delete p; // Clean up memory and fix for next check.test().
-
- p = test_class(1);
- check.test("Test_class(1) error: ");
-
- p = test_class(0);
- check.test("This error is okay: ");
- delete p; // Clean up memory.
-
- check.start(); // Get ready for next call to check.test().
-
- // Next line demonstrates compiler creating temp value.
- test_value(t);
-
- check.testnew("This error is okay: ");
- test_test_value(t);
- check.test("Test_test_value(t) error: ");
- }
-
- char *test_class(const int i)
- {
- char *r;
-
- if (i)
- {
- test_class_good t;
- r = 0;
- }
- else{
- test_class_bad t;
- r = t.p;
- }
- return r;
- }
-
- char *test_easy(const int i)
- {
- char *p = new char;
- if (i) { delete p; p = 0; }
- return p;
- }
-
- void test_value(test_class_good t)
- { /* Ignore warning about t not being used. */ }
-
-
- Listing 4 (chkheap.hpp)
- /*******************************************************************/
- /* Check class allocation errors. Copyright by Joe Schell 1989. */
- /*******************************************************************/
-
- #ifndef CLASS_check_heap
- #define CLASS_check_heap
-
- #include <iostream.h>
- #include <stddef.h> // Used for ptrdiff_t definition.
-
- // CHECK_HEAP_diff_: Used get around segmented memory on IBMs.
- #if defined(_TURBOC_) \
- && (defined(_LARGE_) defined(_HUGE_) defined(_COMPACT_))
- #define CHECK_HEAP_diff_ char huge *
- #else
- #define CHECK_HEAP_diff_ char*
- #endif
-
-
- class check_heap
- {
- public:
- void start() { begin = new char; delete begin; }
- check_heap() { start(); }
-
- void test(const char *s=0) // Do a test.
-
- {
- end = new char;
- if (begin != end)
- cerr << s
- << "Heap error: entry/exit difference = "
- << diff() << ".\n";
- delete end;
- }
-
- void testnew(const char *s=0) // Do a test and reset.
- { test(s); start(); }
-
- private:
- char *begin, *end; // Beginning and end of allocation.
-
- ptrdiff_t diff() const
- { return (ptrdiff_t)
- ((CHECK_HEAP_diff_)end - (CHECK_HEAP_diff_)begin);}
-
- }; // End of check_heap class.
-
- #endif
-
-
- Listing 5 (testbyte.cpp)
- /*****************************************************************/
- /* Test byte and word classes. Copyright Joe Schell 1989. */
- /*****************************************************************/
-
- #include <byte.hpp>
-
- #define comp(c,i) (((c) == int(i)) ? "okay.\n" : "not okay.\n")
-
- void test_init(int c) {cout << " Initialization is " << comp(c,3);}
- void test_inc(int c) { cout << " Increment is" << comp(c,4); }
- void test_dec(int c) { cout << " Decrement is" << comp(c,3); }
- void test_eql(int c) { cout << " Equal for int is " << comp(c,3);}
-
-
- main()
- {
- cout << "Testing byte and word class.\n";
-
- byte b, c=3, *d;
- char *test_byte = "abc";
-
- cout << "Byte:( should be 03, result=" << c.make_string() << ")\n";
- test_init(c);
- c++; test_inc(c);
- c--; test_dec(c);
- b=c; test_eql(b);
- c=4;
- cout << " Setting equal to integer is " << comp(c,4);
-
- b=c;
- c++;
- cout << " Comparison of bytes is"
- << ((b!=c) ? "okay." : "not okay.") << "\n";
- d = (byte*)test_byte;
-
- cout << " Pointing is" << comp(*d,*test_byte);
- d++;
- cout << " Incrementing pointer is " << comp(*d,*(test_byte+1));
-
-
- word x, y=3, *z;
- int test_word=8;
- cout << "\nWord:( should be 0003, result="
- << y.make_string() << ")\n";
- test_init(y);
- y++; test_inc(y);
- y--; test_dec(y);
- x=y; test_eql(x);
- y++;
- cout <<" Comparison of words is"
- << ((x!=y) ? "okay." :"not okay.") << "\n";
- z = (word*)(&test_word);
- cout << " Pointer to word is" << comp(*z,test_word);
- (*z)++;
- cout << " Dereference and increment is " << comp(*z,9);
-
- // The next two lines should cause 'Illegal values' when
- // not commented.
- // b=UCHAR_MAX + 1;
- // x=(long)UINT_MAX + 1;
-
- cout << "\nTest is finished.\n";
- }
-
-
- Listing 6 (byte.hpp)
- /*************************************************************/
- /* Byte classes. Copyright by Joe Schell 1989. */
- /*************************************************************/
-
- #ifndef CLASS_byte
- #define CLASS_byte
- #include <limits.h> // Maximum values UCHAR_MAX and UINT_MAX.
- #include <stdlib.h> // prototype exit() and EXIT_FAILURE.
- #include <iostream.h>
- #include <form.h>
- /*--------------------------------------------------------------------*/
- /* byte Handle a byte. */
- /*--------------------------------------------------------------------*/
- class byte
- {
- public:
- byte() { c = 0; }
- byte(int &i) { c = value(i); }
- operator int() const { return c; }
- byte operator=(int &i) { c = value(i); return *this; }
- byte operator++() { c++; return *this; }
- byte operator--() { c--; return *this; }
- char *make_string() { return form("%2.2X", int(c));}
-
- private:
- unsigned char c; // A byte.
- unsigned char value(int &i)
- {
-
- if (i > UCHAR_MAX)
- {
- cerr << "\nByte class: Illegal value-" << i << "\n";
- exit(EXIT_FAILURE);
- }
- return (unsigned char)i;
- }
- }; // End of byte class.
- /*---------------------------------------------------------------------*/
- /* word Handle a word */
- /*---------------------------------------------------------------------*/
- class word
- {
- public:
- word() { i = 0; }
- word(long &x) { i = value(x); }
- operator long() const { return (long)i; }
- word operator++() { i++; return *this; }
- word operator--() { i--; return *this; }
- char *make_string() { return form("%4.4X", i); }
- private:
- unsigned int i; // An int.
- unsigned int value(long &x)
- {
- if (x > UINT_MAX)
- {
- cerr << "\nWord class: Illegal value-" << x << "\n";
- exit(EXIT_FAILURE);
- }
- return (unsigned int)x;
- }
- }; // End of word class.
-
- #if sizeof(unsigned char) != sizeof(byte)
- #error Byte class cannot be used as pointer to memory.
- #endif
- #if sizeof(unsigned int) != sizeof(word)
- #error Word class cannot be used as pointer to memory.
- #endif
-
- #endif // #ifndef CLASS_byte
-
-
- Listing 7(form.h)
- /******************************************************************/
- /* Formatted output functions. Note: the return value */
- /* is only good until a form function is used again. */
- /******************************************************************/
- #ifndef INCLUDE_form
- #define INCLUDE_form
-
- #ifndef_STDIO_H
- #include <stdio.h>
- #endif
-
- static char _form_s[256];
-
-
- static char *dec(long i,int w=0)
-
- { sprintf(_form_s, "%*ld", w, i);
- return _form_s;
- }
-
- static char *hex(int i,int w=0)
- { sprintf(_form_s, "%0*X", (w ? w : sizeof(int)), i);
- return _form_s;
- }
-
- static char *hex(long i, int w=0)
- { sprintf(_form_s, "%0*X", (w ? w : sizeof(long)), i);
- return _form_s;
- }
- static char *chr(int i,int w=0)
- { sprintf(_form_s, "%*c", w, i);
- return_form_s;
- }
-
- static char *form(char *f,...)
- { vsprintf(_form_s, f, ...); return _form_s; }
-
- #endif
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Standard C
-
-
- Implementing <locale.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 Standard C, which he co-authored with Jim
- Brodie. You can reach him at pjp@plauger.uunet.
-
-
-
-
- Introduction
-
-
- Last month, I introduced the header <locale.h> and described its brief
- history. I showed how to adapt a program to the default locale, and how to
- partially revert behavior to the "C" locale when necessary. Now it's time to
- look at some implementation details.
- The easiest part is the function localeconv. All it must do is return a
- pointer to a structure describing (parts of) the current locale. That
- structure has type struct lconv, which is defined in <locale.h>. Here are the
- easy parts of the implementation. Listing 1 shows the file locale.h. Listing 2
- shows localeco.c. (The name is chopped to eight letters because of file naming
- restrictions on MS-DOS and other systems.) Packed in with localeconv are the
- structures holding the current and "C" locales.
- I have defined additional fields in struct lconv, over and above those
- specified by the C Standard. One field points at the name of the locale.
- Another links these structures together into a list. (Initially, the "C"
- locale is the only entry on the list.) The remaining fields contain
- information that changes with a change in locale. The Standard C library I
- have written defines several such fields. Here, I show only the ones that I've
- discussed in earlier columns. You can see what is involved in controlling the
- tables used by the functions in <ctype. h>.
-
-
- What the Standard Says
-
-
- The setlocale function introduces many more implementation isses than
- localeconv. I showed what the Standard says about localeconv last month. Here
- is what it has to say about setlocale:
-
-
- 4.4.1 Locale Control
-
-
-
-
- 4.4.1.1 The setlocale Function
-
-
-
-
- Synopsis
-
-
- #include <locale.h>
- char *setlocale(int category,
- const char *locale);
-
-
- Description
-
-
- The setlocale function selects the appropriate portion of the program's locale
- as specified by the category and locale arguments. setlocale can change or
- query the program's entire current locale or portions thereof. The value
- LC_ALL for category names the program's entire locale; the other values for
- category name only a portion of the program's locale. LC_COLLATE affects the
- behavior of the strcoll and strxfrm functions. LC_CTYPE affects the behavior
- of the character handling functions101 and the multibyte functions.
- LC_MONETARY affects the monetary formatting information returned by
- localeconv. LC_NUMERIC affects the decimal-point character for the formatted
- input/output functions and the string conversion functions, as well as the
- non-monetary formatting information returned by the localeconv function.
- LC_TIME affects the behavior of the strftime function.
- A value of "C"for locale specifies the minimal environment for C translation;
- a value of " " for locale specifies the implementation-defined native
- environment. Other implementation-defined strings may be passed as the second
- argument to setlocale.
- At program startup, the equivalent of
- setlocale(LC_ALL, "C");
- is executed.
- The implementation shall behave as if no library function calls setlocale.
-
-
-
- Returns
-
-
- If a pointer to a string is given for locale and the selection can be honored,
- the setlocale function returns a pointer to the string associated with the
- specified category for the new locale. If the selection cannot be honored,
- setlocale returns a null pointer and the program's locale is not changed.
- A null pointer for locale causes setlocale to return a pointer to the string
- associated with the category for the program's current locale; the program's
- locale is not changed.102
- The pointer to string returned by setlocale is such that a subsequent call
- with that string value and its associated category will restore that part of
- the program's locale. The string pointed to shall not be modified by the
- program, but it may be overwritten by a subsequent call to setlocale.
- Forward references: formatted input/output functions (4.9.6), the multibyte
- character functions (4.10.7), the multibyte string functions (4.10.8), string
- conversion functions (4.10.1), the strcoll function (4.11.4.3), the strftime
- function (4.12.3.5), the strxfrm function (4.11.4.5).
- Footnotes:
- 101. The only functions in 4.3 whose behavior is not affected by the current
- locale are isdigit and isxdigit.
- 102. The implementation must arrange to encode in a string the various
- categories due to a heterogeneous locale when category has the value LC_ALL.
- [end of excerpt]
-
-
- Implementing setlocale
-
-
- setlocale clearly has a number of tasks to perform. It must determine what
- locales to switch to, based on the category and name you specify when you call
- the function. It must find locales already in memory, or read in newly
- specified locales from a file. (I describe the general case, of course. A
- minimal implementation can recognize only the "C" and " " locales, which can
- be the same.) And it must return a name that it can later use to restore the
- current locale.
- The last task is one of the hardest because you can construct a mixed locale,
- containing categories from various locales. For example, you can write:
- #include <locale.h>
- .....
- char *s1, s2;
-
- setlocale(LC_ALL, "");
- s1 = setlocale(LC_CTYPE, "C");
- if ((s2 = malloc(strlen(s1) + 1)))
- strcpy(s2, s1);
- The first call switches to the native locale, which is some locale preferred
- by the local operating environment. The second call reverts one category to
- the "C" locale. You must make a copy of the string pointed to by s1 because
- intervening calls to setlocale might alter it. If you later make the call
- setlocale(LC_ALL, s2);
- the locale reverts to its earlier mixed state.
- setlocale must contrive a name that it can later use to reconstruct an
- arbitrary mix of categories. The C Standard doesn't say how to do this, or
- what the name looks like. It only says that an implementation must do it.
- The scheme I settled on was to paste qualifiers on a locale name if it
- contains mixed categories. Say, for example, that the base locale is "USA".
- That gives you American date formats, the English alphabet, and so on. But an
- application adapts the monetary category to the special conventions of
- accounting -- a locale exists called "acct". The name that characterizes this
- mixed locale is "USA;monetary:acct".
- I use semicolons to separate components of the mixed locale name. Within a
- component, a colon separates a category name from its locale name. The base
- locale has no category name qualifier. When setlocale constructs a name, it
- adds components only for categories that differ from the base locale.
- Perhaps now you can understand some of the complexity of setlocale. Listing 3
- shows the source file setlocal.c. Much of its logic is concerned with parsing
- a name to determine which locale to use for each category. Another big chunk
- of logic builds a name that setlocale can later digest. Everything else is
- small potatoes by comparison.
- To determine the native locale, I inspect the environment variable LOCALE.
- That strikes me as a reasonable channel for determining what locale to favor.
- It's akin to using the environment variable TZ to determine what time zone
- you're in. The environment variable is inspected at most once during program
- execution.
- You will also see code that copies information into the "C" locale on the
- first call to the function. I adopted that ruse to avoid a nasty snowball
- effect. It's easy enough to pile all the various locale-dependent tables into
- one structure. Do so, however, and you get the whole snowball regardless of
- how little of it you use. I felt it was better to have setlocale do a bit more
- work to avoid this problem. You don't want to drag in 10Kb of code when you
- use only isspace from the library.
- I offloaded some of the work to an internal function called _Getloc. Listing 4
- shows the file xgetloc.c, or at least most of it. This function determines
- whether a locale exists in memory. If a locale does not exist in memory,
- _Getloc should go looking for it. I have stubbed that code out for this
- presentation, because it takes a whole column just to describe how you can
- make your own locale files and read them in at runtime.
- Listing 5 shows the file xsetloc.c. It contains the function _Setloc, which
- actually copies new information into the current locale. It also copies
- information out to the various bits of static data affected by changes in the
- locale. A call to setlocale drags in all this stuff. I don't know how to avoid
- this particular snowball. At least you can avoid it if you leave locales
- alone.
- I have tested this code at least superficially. It should not contain major
- errors. Be wary of small gaffes, however.
-
-
- Conclusion
-
-
- What I have presented here is just the basic machinery you need to support
- locales. It is enough to let you build additional locales directly into the
- library. Just add static declarations of type struct lconv and initialize them
- as you see fit. Be sure to change_Clocale._Next to point at the list you add.
- The real fun of locales is defining an open-ended set. To do that, you must be
- able to specify a locale without altering C code. I have developed
- considerable additional machinery that lets you do so. Next month, I will show
- you the code that reads locale files.
-
- Listing 1 (locale.h)
- /* locale.h standard header */
- #ifndef _LOCALE
- #define _LOCALE
- /* macros */
- #define NULL_NULL
- /* locale codes */
- #define LC_ALL 0
- #define LC_COLLATE 1
- #define LC_CTYPE 2
- #define LC_MONETARY 3
- #define LC_NUMERIC 4
-
- #define LC_TIME 5
- /* ADD YOURS HERE */
- #define _NCAT 6 /* one more than last */
- /* type definitions */
- struct lconv {
- struct lconv *_Next;
- const char *_Name;
- /* controlled by LC_CTYPE */
- const short *_Ctype;
- const short *_Tolower;
- const short *_Toupper;
- /* controlled by LC_MONETARY */
- char *currency_symbol;
- char *int_curr_symbol;
- char *mon_decimal_point;
- char *mon_grouping;
- char *mon _thousands_sep;
- char *negative_sign;
- char *positive_sign;
- char frac_digits;
- char int_frac_digits;
- char n_cs_precedes;
- char n_sep_by_space;
- char n_sign_posn;
- char p_cs_precedes;
- char p_sep_by_space;
- char p_sign_posn;
- /* controlled by LC_NUMERIC */
- char *decimal_point;
- char *grouping;
- char *thousands_sep;
- };
- /* declarations */
- struct lconv *localeconv(void);
- char *setlocale(int, const char *);
- struct lconv *_Getloc(const char *, const char *);
- struct lconv *_Setloc(int, struct lconv *);
- extern struct 1conv _Clocale, _Locale;
- /* macro overrides */
- #define localeconv() (&_Locale)
- #endif
-
-
- Listing 2 (localeco.c)
- /* localeconv function */
- #include <limits.h>
- #include <locale.h>
-
- /* static data for "C" and current locales */
- static char null[] = "";
- struct lconv _Clocale = {
- NULL, "C",
- /* LC_CTYPE */
- NULL, NULL, NULL,
- /* LC_MONETARY */
- null, /* currency_symbol */
- null, /* int_curr_symbol */
- null, /* mon_decimal_point */
- null, /* mon_grouping */
-
- null, /* mon_thousands_sep */
- null, /* negative_sign */
- null, /* positive_sign */
- CHAR_MAX, /* frac_digits */
- CHAR_MAX, /* int_frac_digits */
- CHAR_MAX, /* n_cs_precedes */
- CHAR_MAX, /* n_sep_by_space */
- CHAR_MAX, /* n_sign_posn */
- CHAR_MAX, /* p_cs_precedes */
- CHAR_MAX, /* p_sep_by_space */
- CHAR_MAX, /* p_sign_posn */
- /* LC_NUMERIC */
- ".", /* decimal_point */
- null, /* grouping */
- null, /* thousands_sep */
- struct lconv _Locale = {
- NULL, "C",
- /* LC_CTYPE */
- NULL, NULL, NULL,
- /* LC_MONETARY */
- null, /* currency_symbol */
- null, /* int_curr_symbol */
- null, /* mon_decimal_point */
- null, /* mon_grouping */
- null, /* mon_thousands_sep */
- null, /* negative_sign */
- null, /* positive_sign */
- CHAR_MAX, /* frac_digits */
- CHAR_MAX, /* int_frac_digits */
- CHAR_MAX, /* n_cs_precedes */
- CHAR_MAX, /* n_sep_by_space */
- CHAR_MAX, /* n_sign_posn */
- CHAR_MAX, /* p_cs_precedes */
- CHAR_MAX, /* p_sep_by_space */
- CHAR_MAX, /* p_sign_posn */
- /* LC_NUMERIC */
- ".", /* decimal_point */
- null, /* grouping */
- null, /* thousands_sep */
-
- /* get pointer to current locale */
- #undef localeconv
- struct lconv *localeconv(void)
- {
- return (&_Locale);
- }
-
-
- Listing 3 (setlocal.c)
- /* setlocale function */
- #include <ctype.h>
- #include <locale.h>
- #include <stdlib.h>
- #include <string.h>
-
- #if _NCAT != 6
- #error wrong number of categories
- #endif
- /* static data */
-
- static char *defname = NULL;/* name of "" locale */
- static int namalloc = 0;/* _Locale. _Name allocated
- */
- static const char * const nmcats[_NCAT] = {
- NULL, "collate:", "ctype:", "monetary:",
- "numeric:", "time:"};
- static struct lconv *pcats[_NCAT] = {NULL};
-
- /* set new locale */
- #undef setlocale
- char *setlocale(int cat, const char *lname)
- {
- if (cat < 0 _NCAT <= cat)
- return (NULL); /* bad category */
- if (lname == NULL)
- return ((char *)_Locale._Name);
- if (lname[0] == '\0')
- { /* find name of default locale */
- char *sl, *s2;
-
- if (defname)
- lname = defname;
- else if ((sl = getenv("LOCALE")) != NULL
- && (s2 = malloc(strlen(sl) + 1)) != NULL)
- lname = defname = strcpy(s2, sl);
- else
- lname = "C";
- }
- if (_Clocale._Ctype == NULL)
- { /* flesh out "C" locale */
- size_t i;
-
- for (i = 0; i < _NCAT; ++i)
- pcats[i] = & _Clocale;
- _Clocale._Ctype = _Ctype;
- _Clocale._Tolower = _Tolower;
- _Clocale._Toupper = _Toupper;
- }
- { /* set categories */
- struct lconv *p;
- int changed = 0;
-
- if (cat != LC_ALL)
- { /* set a single category */
- if ((p = _Getloc(nmcats[cat], lname)) == NULL)
- return (NULL);
- if (p != pcats[cat])
- pcats[cat] = _Setloc(cat, p), changed = 1;
- }
- else
- { /* set all categories */
- size_t i;
-
- for (i = 0; ++i < _NCAT; )
- { /* set a category */
- if ((p = _Getloc(nmcats[i], lname)) == NULL)
- { /* revert all on any failure */
- setlocale(LC_ALL,_Locale._Name);
- return (NULL);
-
- }
- if (p != pcats[i])
- pcats[i] = _Setloc(i, p), changed = 1;
- }
- if ((p = _Getloc("", lname)) != NULL)
- pcats[0] = p; /* set only if LC_ALL
- component */
- }
- if (changed)
- { /* rebuild _Locale._Name */
- char *s;
- size_t i, n;
- size_t len = strlen(pcats[0]->_Name);
-
- for (i = 0, n = 0; ++i < _NCAT; )
- if (pcats[i] != pcats[0])
- { /* count a changed subcategory */
- len += strlen(nmcats[i])
- + strlen(pcats[i]->_Name) + 1;
- ++n;
- }
- if (in == 1)
- { /* uniform locale */
- if (namalloc)
- free((void *)_Locale._Name);
- Locale._Name = pcats[1]->_Name;
- namalloc = 0;
- }
- else if ((s = malloc(len + 1)) == NULL)
- { /* may be rash to try to roll back */
- setlocale(LC_ALL, _Locale._Name);
- return (NULL);
- }
- else
- { /* build complex name */
- if (namalloc)
- free((void *)_Locale._Name);
- _Locale. _Name = s;
- namalloc = 1;
- s += strlen(strcpy(s, pcats[0]->_Name));
- for (i = 0; ++i < _NCAT; )
- if (pcats[i] != pcats[0])
- { /* add a component */
- *s = ';';
- s += strlen(strcpy(s, nmcats[i]));
- s += strlen(strcpy(s, pcats[i]->_Name));
- }
- }
- }
- }
- return ((char *)_Locale._Name);
- }
-
-
- Listing 4 (xgetloc.c)
- /*_Getloc function */
- #include <stdio.h>
- #include <string.h>
-
-
- /* get locale pointer, given category and name */
- struct lconv *_Getloc(const char *nmcat, const char *lname)
- {
- const char *ns, *s;
- size_t nl;
- struct lconv *p;
-
- { /* find category component of name */
- size_t n;
-
- for (ns = NULL, s = lname; = s += n + 1)
- { /* look for exact match or LC_ALL */
- if (s[n = strcspn(s, ":;")] == '\0' s[n] == ';')
- { /* memorize first LC_ALL */
- if (ns == NULL)
- ns = s, nl = n;
- if (s[n] == '\0')
- break;
- }
- else if (memcmp(nmcat, s, ++n) == 0)
- { /* found exact category match */
- ns = s + n, nl = strcspn(ns, ";");
- break;
- }
- else if (s[n += strcspn(s + n, ";")] == '\0')
- break;
- }
- if (ns == NULL)
- return (NULL); /* invalid name */
- }
- for (p = &_Clocale; p; p = p->_Next)
- if (memcmp(p->_Name, ns, nl) == 0
- && p->_Name[nl] == '\0')
- return (p);
- /* try here to read in locale from file */
- return (NULL);
- }
-
-
- Listing 5 (xsetloc.c)
- /* _Setloc function */
- #include <ctype.h>
- #include <limits.h>
- #include <locale.h>
- #include <stdlib.h>
- #include <string.h>
-
- /* set category for locale */
- struct lconv *_Setloc(int cat, struct lconv *p)
- {
- switch (cat)
- { /* set a category */
- case LC_COLLATE:
- break;
- case LC_CTYPE:
- _Ctype = p->_Ctype;
- _Tolower = p->_Tolower;
- _Toupper = p->_Toupper;
- break;
-
- case LC_MONETARY:
- _Locale.currency_symbol = p->currency_symbol;
- _Locale.int_curr_symbol = p->int_curr_symbol;
- _Locale.mon_decimal_point = p->mon_decimal_point;
- _Locale.mon_grouping = p->mon_grouping;
- _Locale.mon_thousands_sep = p->mon_thousands_sep;
- _Locale.negative_sign = p->negative_sign;
- _Locale.positive_sign = p->positive_sign;
- _Locale.frac_digits = p->frac_digits;
- _Locale.int_frac_digits = p->int_frac_digits;
- _Locale.n_cs_precedes = p->n_cs_precedes;
- _Locale.n_sep_by_space = p->n_sep_by_space;
- _Locale.n_sign_posn = p->n_sign_posn;
- _Locale.p_cs_precedes = p->p_cs_precedes;
- _Locale.p_sep_by_space = p->p_sep_by_space;
- _Locale.p_sign_posn = p->p_sign_posn;
- break;
- case LC_NUMERIC:
- _Locale.decimal_point = p->decimal_point[0] != '\0'
- ? p->decimal_point : ".";
- _Locale.grouping = p->grouping;
- _Locale.thousands_sep = p->thousands_sep;
- break;
- case LC_TIME:
- break;
- }
- return (p);
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Doctor C's Pointers(R)
-
-
- Data Structures, Part I
-
-
-
-
- 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 uunet!aussie!rex or aussie!rex@uunet.uu.net.
-
-
- This month I embark on a new series on data structures. Probably the single
- biggest strength of C is its wealth of basic and derived data types --
- everything from simple scalars and one-dimensional arrays to arrays of
- pointers to functions returning pointers to arrays of objects.
-
-
- Introduction
-
-
- C is an expensive language to learn. In theory, it should only be pursued
- vigorously if the benefits significantly outweigh the costs. Unless you can
- master the extensive data typing capabilities C provides, you will not be able
- to completely understand or exploit the language. If you don't think you need
- many the data capabilities C provides, you should either use a much safer
- language with less power or learn more about the capabilities C has so you can
- see how you might use them.
- When most people come to a new language, they think in terms of the
- capabilities of their old languages for quite a while. If their old language
- didn't support a given capability (for example, pointers to functions), they
- may not even know about that capability, or have any real idea why they would
- want to use it. So the more educated you can become about C's data structure
- capabilities, the better off you'll be. I hope this series will help.
- I plan to cover the following topics:
- simple arrays and array operations (such as insertion, deletion, and
- searching)
- arrays of pointers versus multi-dimensional arrays
- the use of malloc and friends to extend arrays
- stacks, queues, deques, and linked lists (single, double, circular, and
- lattice)
- recursion
- pointers to functions and tables thereof
- trees
- This is an ambitious agenda, and no doubt there will be digressions along the
- way. However, I will try to adhere to something along these lines.
- If you wish to read a little on your own, I recommend Donald E. Knuth's The
- Art of Computer Programming, Volume 1: Fundamental Algorithms. Addison-Wesley
- ISBN 0-201-03809-9. Chapter 2 (Information Structures) runs some 230 pages and
- is full of useful information on data structures. Although much of this was
- written more than 20 years ago, it is still quite relevant.
-
-
- Simple Array Manipulations
-
-
- The following, rather large, program defines an array of integers. It allows
- the user to manipulate the array either as a whole or by its individual
- elements. (I'll call them nodes.) The complete set of operations are:
- Add a new node to the end
- Change a user-selected node
- Display a user-selected node
- Insert a new node between nodes or before the first node
- Remove a user-selected node
- Search for first occurrence of a node value
- Search for last occurrence of a node value
- Report the number of nodes
- Sort the nodes in ascending order
- Show all entries in the table
- The array is defined with only four nodes. However, by changing the macro
- MAX_NODES you can make the number anything you choose. (I chose four so I can
- easily fill the array and test the error checking for full arrays, etc.)
- I use scanf to get user-supplied input. If you want robust error recovery on
- invalid input, scanf is difficult (if not impossible) to use. That is a
- separate issue, not directly relevant to the main theme. So I have chosen to
- ignore input validation for the most part. If you want, you can easily supply
- input that will break the program.
- The commands are defined using macros, so you can change the characters used.
- You could also obtain input using a mouse-driven menu. Again, that is
- peripheral to the main topic.
- Throughout the series, I will adapt this program to use different data
- structures behind the scenes. For example, in this first part I use a simple
- automatic one-dimensional array. Next, I use one allocated at runtime via
- malloc. Eventually, in a future installment, I will use a dynamically
- allocated linked list.
- Listing 1 shows a program that allocates memory for an array ary:
- int ary[MAX_NODES];
- It permits the user to perform various operations on the whole array or on
- specific elements (or nodes) within it. They may, for example
- add, remove, or change a node
- display or sort the whole array
-
- The program is designed as an introductory example of using data structures
- with C.
- I will not discuss the code much because it is clearly laid out and the
- identifier names are self-explanatory. I tested the program by creating a text
- file of commands mixed with various expected and unexpected inputs. I then fed
- that to the program using command-line redirection. This is not only
- convenient for testing, but allows for a crude batch facility to process large
- amounts of production data acquired through other means.
- You might take issue with my use of goto in the cases SEARCH_FNODE and
- SEARCH_LNODE. I find many programmers of the structured programming faith (of
- which I am one) taking their dislike for goto to the extreme. If you think
- goto should never be used, I suggest you think how to build a machine that
- does not have branch or jump instructions. Even Pascal, supposedly the
- ultimate structured language, has a goto.
- The problem with goto is that programmers inevitably write code that branches
- to irresponsible places -- that's why C's break and continue are so nice. The
- compiler works out where to go instead. In any event, I believe my goto
- solution as presented in Listing 1 is more elegant and efficient than any
- structured solution I've seen for the problem. My rule is, "Use goto only when
- absolutely necessary and then only in a forward direction in a local scope."
- End of sermon on goto.
-
-
- Dynamically Allocated Arrays
-
-
- The first version of the program defined ary as having automatic storage
- duration. This was an arbitrary choice -- it could just as easily have been
- static for my purposes.
- Standard C provides a family of routines to allocate memory at runtime. I will
- refer to this as dynamic memory allocation. (See my column "The Memory
- Management Library" in CUJ Vol. 8, No. 1.) With careful design, you can easily
- change from using an automatic or static object to a dynamic one. The only
- changes you need to make to the program are as follows:
- #include <stdlib.h>
-
- main()
- {
- int *ary;/* ary is now a pointer, NOT an array */
- ...
- ary = malloc(MAX_NODES * sizeof(int));
- if (ary == NULL) {
- printf ("Cannot allocate memory for ary\n");
- exit(2);
- }
- You need the header stdlib.h to declare the memory allocation functions. Since
- the array does not exist at compile-time, ary is now simply a pointer to an
- int, not an array of int. By allocating the same amount of memory as before,
- but now at runtime, you can make ary point to the first element in the
- allocated array. Where ary was previously the name of an array and was
- converted to &ary[0] in all the right places, it is now the address of the
- first int in the allocated space. In short, ary can be used in exactly the
- same way as before. (This is possible since C permits a pointer expression,
- such as ary, to be arbitrarily subscripted to one level. Because a[i] is
- equivalent to *(a + i), the two subscripted forms are completely
- interchangeable.)
-
-
- Variable Size Arrays
-
-
- Using malloc is a step in the right direction, but the array is still limited
- to a fixed number of nodes. You can fix this by replacing the constant
- MAX_NODES with a variable max_nodes and using realloc to extend the array if
- it fills. Again, with the proper initial design, the changes needed are small
- and localized. For example:
- int *ptr;
- int max_nodes = 1;
-
- ary = malloc(max_nodes * sizeof(int));
- if (ary == NULL) {
- printf ("Cannot allocate initial memory"
- "for ary\n");
- exit(2);
- }
- By initializing max_nodes to 1, you start out allocating an array of only one
- element. Of course, if your application always used at least 20, for example,
- this initializer should be changed. There's no point extending the array by
- one node 20 times if you know up front what you're going to do.
- The only other changes are to the cases ADD_NODE and INSERT_NODE. Instead of
- complaining when the table is full, these operations can now extend the array
- by one node, as follows:
- if (nodes_in_use == max_nodes) {
- ptr = realloc(ary,
- (max_nodes + 1) * sizeof(int));
- if (ptr == NULL) {
- printf("Cannot allocate new
- node\n");
- break;
- }
- ary = ptr;
- ++max_nodes;
- }
- Now you have a version that is limited only by the amount of memory available
- at runtime. Note though that realloc might not be able to allocate memory
- contiguously, in which case it has to go the more expensive route of
- allocating a new space, copying the old array contents to it, and freeing the
- old copy. realloc has another limitation. Say, for example, there are 1,000
- bytes of memory available to begin with. Once the allocated array uses about
- 500 of these, there is insufficient memory left for realloc to make a copy
- before it frees the original. While only half of available memory is used,
- realloc might not be able to use the other half.
- realloc lets you extend the array by one node with a very small amount of
- code, but it is probably not efficient over the long run. You might want to
- extend the array by a cluster of nodes (say 5 to 10 at a time). Or you might
- chose to use a linked list instead to avoid the need for realloc to copy any
- of the existing array.
- In my realloc version, I chose not to shrink the array when a node was
- deleted. This was an arbitrary choice. You could argue that it would be good
- housekeeping to do so. You could also argue that by not freeing deleted nodes
- you can maintain a cache of available nodes when the next addition or
- insertion is needed.
-
-
- Final Comments
-
-
-
- All examples in this article work and are useful, but they probably have
- inefficiencies. For example, inserting and deleting nodes requires all
- subsequent nodes to be shuffled toward or away from the end, respectively. As
- the node count increases, these operations become increasingly expensive. You
- can avoid this expense by using linked lists. You will see in future
- installments that such lists have their own unique problems.
-
- Listing 1
- #include <stdio.h>
- #include <ctype.h>
- #include <limits.h>
-
- #define MAX_NODES 4 /* max number of nodes in array */
-
- #define ADD_NODE 'A'
- #define CHANGE_NODE 'C'
- #define DISPLAY_NODE 'D'
- #define EXIT 'E'
- #define SEARCH_FNODE 'F'
- #define HELP '?'
- #define INSERT_NODE 'I'
- #define SEARCH_LNODE 'L'
- #define COUNT_NODES 'N'
- #define REMOVE_NODE 'R'
- #define SORT_NODES 'S'
- #define DUMP_TABLE 'T'
-
- /* ------------------------------------------------------------------- */
-
- main()
- {
- int ary[OB]MAX_NODES[CB];
-
- int nodes_in_use = 0;
- int index;
- int temp, i, j;
- int inchar = 'x'; /* force initial prompt */
-
- while (1) {
- if (inchar != ' ')
- printf("\nEnter Action Code (%c for help): ", HELP);
- switch(toupper(inchar = getchar())) {
-
- /* ------------------------------------------------------------------- */
-
- case EXIT:
- goto end; /* break won't do so use goto */
-
- /* ------------------------------------------------------------------- */
-
- default:
- printf("\n Invalid command. Please try again\n");
- break;
-
- /* ------------------------------------------------------------------- */
-
- case HELP:
- printf("\n The action codes are:\n");
- printf("\t%c - produces this help message\n", HELP);
- printf("\t%c - Add a new node to the end\n", ADD_NODE);
- printf("\t%c - Change a user-selected node\n", CHANGE_NODE);
- printf("\t%c - Display a user-selected node\n", DISPLAY_NODE);
- printf("\t%c - Exit this program\n", EXIT);
- printf("\t%c - Search for first occurrence\n", SEARCH_FNODE);
-
- printf("\t%c - Insert a new node\n", INSERT_NODE);
- printf("\t%c - Search for last occurrence\n", SEARCH_LNODE);
- printf("\t%c - Report the number of nodes\n", COUNT_NODES);
- printf("\t%c - Remove a user-selected node\n", REMOVE_NODE);
- printf("\t%c - Sort the nodes in ascending order\n", SORT_NODES);
- printf("\t%c - Show all entries in the table\n", DUMP_TABLE);
- break;
-
- /* ------------------------------------------------------------------- */
-
- case '\n': /* ignore white space on input */
- case ' ' :
- case '\t':
- case '\v':
- case '\f':
- inchar = ' '; /* indicate white space input */
- break;
-
- /* ------------------------------------------------------------------- */
-
- case ADD_NODE:
- if (nodes_in_use == MAX_NODES) {
- printf("\n Table is full\n");
- break;
- }
-
- printf("\n Enter new node's value: ");
- scanf("%3d", &ary[nodes_in_use]);
- ++nodes_in_use;
- printf("\n Node added");
- break;
-
- /* ------------------------------------------------------------------- */
-
- case DUMP_TABLE:
- if (nodes_in_use == 0) {
- printf("\n Table contains no nodes\n");
- break;
- }
-
- printf("\n Table nodes are as follows:\n");
- for (index = 0; index < nodes_in_use; ++index)
- printf("\tNode %2d =>%3d\n", index ary[index]);
-
- break;
-
- /* ------------------------------------------------------------------- */
-
- case COUNT_NODES:
- printf("\tThere are %2d nodes in the table\n", nodes_in_use);
- break;
-
- /* ------------------------------------------------------------------- */
-
- case DISPLAY_NODE:
- if (nodes_in_use == 0) {
- printf("\n Table contains no nodes\n");
- break;
- }
-
-
- while (1) {
- printf("\n Enter node number: ");
- scanf("%d", &index);
- if (index >= 0 && index < nodes_in_use)
- break;
-
- printf("\n No such node (%d) in table\n", index);
- }
-
- printf("\tNode %2d => %3d\n", index, ary[index]);
- break;
-
- /* ------------------------------------------------------------------- */
-
- case CHANGE_NODE:
- if (nodes_in_use == 0) {
- printf("\n Table contains no nodes\n");
- break;
- }
-
- while (1) {
- printf("\n Enter node number: ");
- scanf("%d", &index);
- if (index < 0 index >= nodes_in_use)
- printf("\n No such node (%d) in table\n", index);
- break;
- }
-
- printf("\tNode %2d => %3d\n", index, ary[index]);
- printf("\n Enter new value: ");
- scanf("%3d", &ary[index]);
- printf("\n Node changed");
- break;
-
- /* ------------------------------------------------------------------- */
-
- case SEARCH_FNODE:
- if (nodes_in_use == 0) {
- printf("\n Table contains no nodes\n");
- break;
- }
-
- printf("\n Enter search value: ");
- scanf("%d", &temp);
-
- for (index = 0; index < nodes_in_use; ++index) {
- if (ary[index] == temp) {
- printf("\tValue %3d found in node %3d\n",
- temp, index);
- goto found1;
- }
- }
-
- printf("\n No such value (%d) in table\n", temp);
- found1:
- break;
-
- /* ------------------------------------------------------------------- */
-
-
- case SEARCH_LNODE:
- if (nodes_in_use == 0) {
- printf("\n Table contains no nodes\n");
- break;
- }
-
- printf("\n Enter search value: ");
- scanf("%d", &temp);
-
- for (index = nodes_in_use - 1; index >= 0; --index) {
- if (ary[index] == temp) {
- printf("\tValue %3d found in node %3d\n",
- temp, index);
- goto found2;
- }
- }
-
- printf("\n No such value (%d) in table\n", temp);
- found2:
- break;
-
- /* ------------------------------------------------------------------- */
-
- case SORT_NODES: /* simple bubble sort */
- if (nodes_in_use == 0) {
- printf("\n Table contains no nodes\n");
- break;
- }
-
- for (i = nodes_in_use - 2; i >= 0; --i) {
- for (j = 0; j <= i; ++j) {
- if (ary[j] > ary[j + 1]) {
- temp = ary[j];
- ary[j] = ary[j + 1];
- ary[j + 1] = temp;
- }
- }
- }
- printf("\n Nodes sorted");
- break;
-
- /* ------------------------------------------------------------------- */
-
- case INSERT_NODE:
- if (nodes_in_use == 0) {
- printf("n\ Table contains no nodes\n");
- break;
- }
-
- if (nodes_in_use == MAX_NODES) {
- printf("\n Table is full\n");
- break;
- }
-
- while (1) {
- printf("\n Enter number of node to insert before: ");
- scanf("%d", &index);
- if (index >= 0 && index < nodes_in_use)
-
- break;
-
- printf("\n No such node (%d) in table\n", index);
- }
-
- ++nodes_in_use;
- for (i = nodes_in_use - 1; i > index; --i) {
- ary[i] = ary[i - 1];
- }
- printf("\n Enter new node's value: ");
- scanf("%3d", &ary[index]);
- printf("\n Node inserted");
- break;
-
- /* ------------------------------------------------------------------- */
-
- case REMOVE_NODE:
- if (nodes_in_use == 0) {
- printf("\n Table contains no nodes\n");
- break;
- }
-
- while (1) {
- printf("\n Enter node number: ");
- scanf("%d", &index);
- if (index >= 0 && index < nodes_in_use)
- break;
-
- printf("\n No such node (%d) in table\n", index);
- }
-
- for (i = index; i < nodes_in_use; ++i) {
- ary[i] = ary[i + 1];
- }
- --nodes_in_use;
- printf("\n Node removed");
- break;
-
- /* ------------------------------------------------------------------- */
-
- }
- }
- end: return (0);
- }
-
- /* ------------------------------------------------------------------- */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Questions and Answers
-
-
- More Pointer Problems
-
-
-
-
- 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) 493-4390. When you hear the answering message,
- press the * button on your telephone. Ken also receives email at
- kpugh@dukemvs.ac.duke.edu (Internet).
-
-
- Q
- I have a wall full of technical manuals, none of which even begins to discuss
- the OBJ file format. Can you tell me where I can get information on OBJ file
- internals?
- Dennis Taylor
- Vancouver, BC
- CANADA
- A
- You did not say which system you are working on. The Intel Relocatable Object
- Module Format is in the back of the Microsoft MS-DOS Programmer's Reference
- Manual. The date on this edition is 1984. Perhaps they have removed it from
- the later editions.
- Q
- I hope you can help me. I have a problem compiling a certain C source code
- (Listing 2) with Microsoft Quick C.
- The file is DSAT.C, a dynamic string array test program. It consists of three
- functions plus a main to coordinate them. The first, wordcount, is flawless.
- The second, str_to_ptrarray, does not work the way I want it to, and I do not
- know why.
- The str_to_ptrarray function should break the passed string into words
- (characters separated with a space) and compose a null-terminated string array
- of the words in the pointer array supplied. The function returns the number of
- words.
- Hence,
- int nwords;
- char mystr[ ] = "This is a test.",
- **strlist;
- nwords = str_to_ptrarray(mystr,
- &strlist);
- should return the values with results as in Listing 1.
- The function allocates all space necessary for the transformation and I want
- to keep it that way! I do not want to have to calculate memory usage each time
- I call the function separately -- I want the function to handle it.
- Inside the function, it works perfectly with QuickC 2.00 (as you can see with
- my four lines of debugging), but outside the function (back in main, for
- example) the work has gone poof. I get the first element along with a null
- pointer assignment runtime error (R6001). With QuickC 2.51, it does not seem
- to do anything, and the compiler gives me a Near Pointer Error.
- Then free_ptrarray simply frees the memory allocated by the str_to_ptrarray
- function.
- I have looked at this for too long. I need your help. Why does this not work?
- Am I missing a fundamental rule of C programming? Please please please help!
- Your time is profoundly appreciated. Thank you.
- Anthony Whitford
- Sidney, BC
- CANADA
- A
- Ah, triple pointers -- more than twice as bad as double pointers. I have to
- admit, it took me a few minutes to figure out what was wrong with your
- program. I got slightly different errors the first time I ran it, but that was
- due to mistyping. For future reference, if you or another reader has a
- problem, please send the code on a 5.25-inch MS-DOS disk.
- Your problem revolves around the precedence of the index operator [ ] and the
- indirection operator *. The former has higher precedence.
- *ptrarray [count]
- is evaluated as:
- * (ptrarray [count])
- or equivalently as:
- * (* (ptrarray + count))
- This adds the value of count to ptrarray before doing the indirection.
- What you want is:
- (*ptrarray) [count]
- which is equivalent to:
- * ((*ptrarray) + count)
- Simply replace all instances of *ptrarray[x] with (*ptrarray)[x], and your
- problems should be solved.
- The lines below /* THIS IS A NEW PRINTF */ clarify the difference. Run your
- program again and see what values are printed out. (If you use the large
- memory model, use "%lx".)
- Two brief comments on the code. First, I dislike dealing with anything more
- than double pointers. You can eliminate the use of the double pointer (except
- for the actual assignment to the parameter) by having a local variable called
- local_ptr_array and declared as:
- char **local_ptr_array;
- The code would read like:
-
- if ((local_ptr_array =
- (char **)calloc(words + 1,...)))
- . . .
- local_ptr_array[index] = . . .
- At the end, simply code:
- /* Set the address in the parameter */
- *ptr_array = local_ptr_array;
- This would have eliminated your problems, because no indirection operator
- would have been involved.
- Second, I would replace the calls to calloc as:
- (*ptrarray) [index] =
- (char *)calloc((size_t)(strlen(cptr) + 1),
- sizeof (**ptrarray [index] ) );
- with:
- (*ptrarray) [index] =
- (char *)calloc((size_t)(strlen(cptr) + 1),
- sizeof(char));
- Since you cast the return to char *, the size of the elements should be the
- size of a char. Although your approach is technically correct, it appears
- slightly less readable.
- Q
- I need to be able to execute a program (Listing 3) in UNIX, and no matter what
- the return value for that program is, I need to return zero (success). This is
- because my menu system beeps and displays unwanted messages when the user hits
- the interrupt key to abort the program.
- My solution is to write a shell program that performs the following:
- 1) fork a process
- 2) if parent then
- 2.1. ignore interrupt
- 2.2. wait for child to finish
- 3) if child then
- 3.1. execute the program passed as an argument
- 4) return zero
- The problem is that if the interrupt key is pressed, then the program returns
- 2000 instead of zero. If the program ends normally, then the program returns
- zero (fine). Also, if I instruct the program to return any value other than
- zero, it does so even if the interrupt key is pressed (as it should).
- Tim Riley
- Miami Lakes, FL
- A
- Good question. I tried it on my UNIX machine (a Sequent) and got exactly the
- same results as you did. It looks OK to me (either that or I'm tired out after
- considering triple pointers). Any thoughts out there? (KP)
-
-
- Reader's Replies
-
-
-
-
- The Function Prototype
-
-
- I write regarding Firdaus Irani's Q?/A! question about his problems with the
- compare function for qsort in Turbo C++ (which you duplicated in Microsoft C).
- I may have missed something, but my understanding is that qsort is defined as
- accepting a pointer to a function that returns an int and takes two const void
- * parameters. Thus, when Mr. Irani defined his function as taking two unsigned
- char ** parameters, there was a mismatch. I have used the qsort function under
- Turbo C, Turbo C++, Microsoft C 5.1 and 6.0, and IBM C/400 (on the AS/400),
- and the implementation has always been as described. Further, the
- documentation with both Turbo C(++) and Microsoft C state that qsort is
- defined in ANSI C.
- To use qsort as Mr. Irani desires, he should declare his function as:
- int comp (const void *, const void *);
- and call qsort just as he did. His comp function should be as follows:
- int comp (void const *a, void const *b)
- {
- return (strcmp(*((unsigned char **) a),
- *((unsigned char **) b));
- }
- The casts take care of the warnings on the parameters to the strcmp function.
- The important thing to note here is that the const void * parameters to the
- compare function of qsort are so that any type of item can be compared. For
- instance, in a utility I have written, I have a structure I use for building a
- typical directory tree, defined as follows:
- typedef struct
- {
- char achPathName[MAXPATH];
- //MAXPATH is defined in the C header files
- char achPathPic[MAXPATH];
- unsigned short usPath Level;
-
- } DIR_INFO;
- This structure is used to hold a directory path name (such as C: \TC\INCLUDE),
- a path "picture" that corresponds to the path name (such as -- --INCLUDE), and
- the level of the directory, the root being 0. An array of these structures is
- declared as:
- DIR_INFO stDirInfo[MAX_DIRS];
- //MAX_DIRS is a #define
- After filling this array using findfirst and findnext, I want to sort it
- alphabetically (yet maintain the proper levels). To do this I declare a
- compare function for qsort:
- int DirSort(const void *, const void *);
- I then call qsort with:
- qsort(stDirInfo, usCurNumDirs,
- sizeof(stDirInfo[0]), DirSort);
- //usCurNumDirs = # of directories found
- The DirSort function is shown in Listing 4.
- Using the (DIR_INFO *) cast, I can sort the array of structures based on a
- structure member.
- Again, I may have missed something, but I thought the usage was
- straightforward. Despite PJP's note to the contrary, I certainly don't see how
- this is a bug in Turbo C++, especially if this is the ANSI definition for the
- qsort function.
- A. Donnie Hale, Jr.
- Hilliard, OH
- Regarding the TC++ question from Firdaus Irani, I have come across the same
- question and solved it in Listing 5. As I think about it, I think that TC++ is
- correct, because as I understand C++ will not automatically cast a void
- pointer, it must be done explicitly. The following works correctly and does
- not require changes to the stdlib.h.
- I greatly appreciate your column, many of the questions hit close to home.
- Jay Holovacs
- Warren, NJ
- In regard to a question from Firdaus Irani in the December 1990 issue of the C
- User's Journal, page 92, using qsort.
- The problem is not in the Turbo C++, but in Firdaus Irani's usage of the
- function. First, any compare function always has the prototype:
- int compare(const void *,
- const void *);
- The arguments to the compare function are then cast to the appropriate type
- within the user-defined compare function.
- My example (see Listing 6) has three different sort functions. I read in a
- list of student names and their corresponding grades. The three data
- structures are:
- n array of a structure containing the students' names and grades,
- n array of the students' names, and
- n array of the students' grades.
- Each structure is sorted (the array of structure is sorted by its grade entry)
- and the results were printed.
- Input file used:
-
- Martin 3.5
- Sheila 4.0
- Marcel 2.7
- Henry 2.9
- Kemberly 3.8
- Cindy 1.7
- The resulting output file (sorted structure, sorted names, sorted grades):
- Cindy 1.70 Cindy 1.70
- Marcel 2.70 Henry 2.70
- Henry 2.90 Kimberly 2.90
- Martin 3.50 Marcel 3.50
- Kimberly 3.80 Martin 3.80
- Sheila 4.00 Sheila 4.00
- The user must cast the argument type within the compare function (see the
- compare function definitions). I do not think that there is a bug in Turbo
- C++'s qsort.
- Martin Schlapfer
- Scotts Valley, CA
- I have enclosed a question and answer from your December 1990 C Users Journal
- column. I think both you and P.J. Plauger missed the boat on this one.
- F. Irani asks why he gets an error when attempting to use qsort. He changed
- the prototype for qsort in stdlib.h to get rid of the error message (!) and
- asks "Does that mean I have to make changes to stdlib every time I try to
- compile a program using qsort that calls a compare function with different
- parameter types?". He says he got no help from Borland tech support. I don't
- think he got any from you or PJP either.
- I think you should have answered him like this:
- Don't change your stdlib.h. The prototype for qsort indicates what qsort
- expects from you, and fooling the compiler is not the answer. The answer is to
- give qsort just what it expects: a compare function that is defined to receive
- void * pointers. If you are actually comparing unsigned char **, then you must
- use a cast inside your compare function.
- For example your comp should have been:
- int comp(const void *a,
- const void *b)
- {
- return strcmp((char *)*(unsigned
- char **)a, (char *)*(unsigned
- char **)b);
- }
-
- Note the casts to char *. This is because strcmp is defined to expect char *,
- even though it is guaranteed to compare the strings as if they are unsigned
- characters. (See sec. 4.11.4 in the ANSI standard.)
- Now, aside from the annoying error messages, why is it wrong to define the
- compare function to accept something other than void * pointers?
- When you call a function, you must pass it what it expects. Likewise, when a
- library function calls your function, your function must expect what the
- library function passes. There is nothing in the standard to prevent an
- implementation from having different representations for different types of
- pointers (except that void * pointers must have the same representation and
- alignment requirements as char * pointers, according to sec. 3.1.2.5). If a
- function call passes void * and the function is defined to expect unsigned
- char **, anything can happen. This is potentially just as dangerous as passing
- an int to a function expecting a long. The function picks up what it expects
- from the stack (or registers), and it had better be right. The whole purpose
- of the prototype system is to protect against mistakes like these, and
- arbitrarily changing a prototype to get rid of an error or warning message is
- like turning off an alarm system because it keeps going off. Better to find
- the problem, understand it, and fix it.
- P.S. It occurs to me that some of the misunderstanding may be due to a
- perception that, since void * pointers may be arbitrarily assigned to and from
- other pointer types without casts, that they may also be passed to a function
- expecting a different pointer type, or that a function defined to accept a
- void * may be passed a different type. This is wrong. The compiler "knows" how
- to deal with assignments and can do the necessary casting automatically. The
- compiler can automatically cast a pointer when it is passed, if a prototype is
- in scope. But in the case of qsort, the calling is being done "blindly" by
- qsort, which has no knowledge of what it is really dealing with. So it passes
- void * pointers. There is no way for the compiler to automatically convert
- these on the "receiving end."
- Raymond Gardner
- Englewood, CO
- You are right that there are some misconceptions with pointers to void. C++
- has tightened the usage of void pointers, so that assignment requires a cast,
- but Standard C permits assignments without casts. For the benefit of our
- readers, let's examine the uses for void pointers. The major area is in places
- where char * was employed before. For example, the prototype for memset is:
- memset(void *address, int byte,
- size_t length);
- We can pass memset something like:
- struct s_test
- {
- int member;
- . . .
- };
- struct s_test test;
- memset(&test, 0, sizeof(test));
- You do not have to cast something like this:
- memset( (void *) &test, 0,
- sizeof(test));
- because void * is assignment-compatible with any other pointer.
- Now let's take the case of qsort. Suppose you want to compare structures of
- s_test type in a function that would be passed to qsort. Your function must be
- prototyped as
- compare_test_struct(const void *one,
- const void *two);
- to meet the _fcmp(const void *, const void *) requirement in the qsort
- prototype.
- The function would need to look like:
- compare_test_struct(const void *one,
- const void *two)
- {
- if ( ((struct s_test *) one) ->
- member > ((struct s_test *)
- two) -> member)
- return 1;
- . . .
- }
- or
- compare_test_struct(const void *one,
- const void *two)
- {
- struct s_test *one_test = one;
- struct s_test *two_test = two
- if ( one_test->member >
- two_test -> member)
- return 1;
- . . .
- }
- Either way appears to make more hidden the real purpose of the function - to
- compare two structures. The parameters one and two will have to contain valid
- beginning addresses for structures. Of course, qsort is designed to supply
- valid ones.
- Note that you can call qsort with any function that expects two void pointers
- and the prototype will not complain. With the above and your example function,
- you could code:
- struct s_test struct_array[10] =
- { /* Some list */... };
- qsort (struct_array, 10,
- sizeof(struct s_test), comp);
- or
- char *array_of_strings[10];
- qsort(array_of_strings, 10,
- sizeof(char *), compare_test_struct);
- These are both equally valid prototype-wise, but absolutely wrong. You may get
- a storage access error in the latter case, if the variable alignment is not
- proper. The prototype for qsort in this case has not protected you from
- shooting yourself in the foot.
- The compare functions as coded above are not useful for general usage, as they
- do not expect pointers to structures of s_test type. You could do the same
- trick as you did for strcmp. That is, you could code the functions as:
- compare_test_struct_for_qsort(const void *one,
-
- const void *two)
- {
- return compare_test_struct_normal(
- (struct s_test *) one,
- (struct s_test *) two);
- }
-
- compare_test_struct_normal(struct s_test *one,
- struct s_test *two)
- {
- if ( one->member > two->member)
- return 1;
- . . .
- }
- Then if you wanted to use the comparison function in a normal mode, you would
- code:
- struct s_test test_a, test_b;
-
- compare_test_struct_normal (&test_a, &test_b);
- And if you wanted to call qsort, you would use:
- qsort(struct_array, 10, sizeof(struct s_test),
- compare_test_struct_for_qsort);
- However, the overhead of a double function call will cut down the efficiency
- of the sort.
- Since it appears that using a prototype of the form:
- int strcmp(const void *, const void *)
- where qsort is called will eliminate the prototype error, I would opt for that
- solution to this tiny incongruity in the prototype system. (KP)
- [All of these responses are on the money. They also cast useful light on a
- notoriously thorny topic. Tom Plum forced X3J11 to consider the problems
- surrounding the prototype for qsort at some length. We never did resolve the
- issue to his satisfaction.
- I can't determine whether my response was incorrect. My back issues of CUJ are
- half a world away. I recall responding to a specific diagnostic that Mr. Irani
- showed. I have caught the C part of Turbo C++ out in this area, and the Gnu C
- compiler as well. It was easy for me to believe that Turbo C could botch type
- checking involving void pointer arguments. Whether or not it does, these
- letters address the basic issue much better than I did. Thanks. (PJP)]
-
-
- Automatic Buffers
-
-
- I'm writing about the sticky automatic buffer that was the subject of Doug
- Oliver's 9/90 CUG and John Brand's 1/91 CUG letters - used in the function
- repeat_format. This function returns a pointer to local storage that was not
- guaranteed to be valid. I was hoping someone else would point out the reason
- why this technique should never be used, but no one did. Hence this letter.
- Using this technique will introduce some of the hardest-to-find bugs you'll
- ever see. The reason automatic storage isn't guaranteed valid is because an
- interrupt service routine (ISR) occurring at precisely the right moment will
- trash the buffer.
- One of the reasons the bugs will be hard to find is that standard PC
- interrupts (e.g., the clock) seem to switch to their own stacks after using
- only 10 bytes or so of the user stack. Other drivers (e.g., an ethernet drive)
- may or may not use a local stack. Depending on the PC's hardware
- configuration, the buffer may be trashed seemingly at random with a
- probability proportional to the frequency of interrupts and the length of the
- delay before the user function copies the buffer to "safe" storage. Thus, the
- program may always work just fine on the author's machine but may develop
- annoying random quirks after it is distributed.
- Some CPUs (e.g., 68030) have a separate stack for interrupts and are not
- subject to this problem. However, the operating system may reclaim the stack
- space for use elsewhere, causing sporadic memory protection faults.
- In any case, this technique is non-portable. I suggest that it be replaced
- with a call to malloc or strdup.
- As long as I'm writing, I'll throw in my contribution to the external variable
- declaration dialog (Andreas Lang, Bill Sharar II, David Hanson, Larry
- Leonard). Listing 7 shows a technique I've used for years.
- This technique is similar to Mr. Leonard's solution. Your initialization of:
- array[15] INIT(
- 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15} );
- doesn't work in my system (And I'm assuming others. Did you compile your
- example?) because the preprocessor does not honor the braces around the
- aggregate initializer in the macro instantiation, instead assuming that the
- commas separate actual arguments. Compound data objects can be declared as w
- is in my example or using a variation of the technique you presented as
- Listing 2 (1/91 CUG).
- Don Drantz
- Eden Prairie, MN
- I agree with you on the use of automatic storage. I prefer for the user to
- pass the address of a buffer that would be filled in with the appropriate
- information. Using malloc inside a function without a warning that memory
- might not be available is not a wise idea. Of course, one could return the
- address of a static buffer (even some library routines do it), but that makes
- multi-user libraries not feasible.
- I have to admit I didn't compile my example. It was so ugly that I didn't have
- the heart to send it to the compiler. (KP)
-
- Listing 1
- nwords is equal to 4,
- mystr is not changed "This is a test.",
- *strlist[0] points to "This",
- *strlist[1] points to "is",
- *strlist[2] points to "a",
- *strlist[3] points to "test.",
- *strlist[4] points to NULL.
-
-
- Listing 2
- /* ***************************************************
-
- DSAT.C Dynamic String Array Test
- Version 1.0
- Created by: Anthony Whitford
- (c) 1990
- *********************************************** */
-
- #include <process.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
-
- typedef char ** ptrarray_t;
-
- int wordcount (const char *);
- int str_to_ptrarray (const char *, char ***);
- void free_ptrarray (char ***);
-
- void main (void)
- {
- int numofitems, retval;
- ptrarray_t strlist;
- if ((retval = str_to_ptrarray("This is a test.", &strlist)) == -1
- printf("Error turning the string into a pointer list.");
- else
- {
- /* Test the data. */
- printf("\nString list outside function...\n");
- for (numofitems = 0; strlist[numofitems] !=NULL; numofitems++)
- {
- printf("%u --> ***%s***\n",
- numofitems, strlist[numofitems]);
- }
- puts("");
- free_ptrarray(&strlist); /* Free pointer array. */
- }
- exit (retval); /* Return number of words or error. */
- }
-
- /* *******************************************************
- Function: wordcount
- Parameters: const char *str -> the string.
- Description: Counts the number of words in a string p to
- 255 bytes.
- Returns: The number of words in astring.
- ****************************************************** */
-
- int wordcount (const char *str)
- {
- int words = 1; /*Initialize word cunt. */
- char tstr[256]; /*Declare a temporary string. */
- strcpy(tstr, str); /* Make a working copy. */
- if (strtok(tstr, "") == NULL) /* If string is only */
- return (0); /* spaces, return 0. */
- while (strtok(NULL, " ") !=NULL)
- words++; /* Count how many words. */
- return (words); /* Return number of words in string. */
- }
-
- /* ************************************************************
-
- Function: str_to_ptrarray
- Parameters: const char *orgstr -> the original string
- to be parsed,
- char ***ptrarray -> the address of the
- pointer array.
- Description: Breaks a string into its words and composes
- a pointer array of the words.
- Returns: "ptrarray" becomes a NULL terminating pointer
- array, and pointer array, or -1 if an error
- occurred.
- ********************************************************* */
-
- int str_to_ptrarray (const char *orgstr, char ***ptrarray)
- {
- unsigned index = 0, words;
- char *cptr,*thestr;
- if (( thestr = (char *) calloc((size_t)(strlen(orgstr) + 1),
- sizeof(*thestr))) == NULL)
- return (-1); /* Make a working copy of the
- string. */
- strcpy(thestr, orgstr);
- words = wordcount(thestr); /* Get number of words. */
- if ((*ptrarray = (char **) calloc(words + 1,
- (size_t) sizeof(**ptrarray))) ==NULL)
- {
- free(thestr);
- return (-1); /* Error allocating space for
- ptrarray.*/
- }
-
- /* THIS IS A NEW PRINTF */
- for (index = 0; index < words; index ++)
- {
- printf("\n & *ptrarray[index] %x & (*ptrarray)[index] %x",
- ptrarray[index], &((*ptrarray)[index]));
- }
- index = 0;
-
- cptr = strtok(thestr, "");
- while (index < words)
- {
- if ((*ptrarray[index] = (char *)calloc((size_t)(strlen(cptr) + 1),
- sizeof(**ptrarray[index]))) == NULL)
- {
- free_ptrarray(ptrarray);
- free(thestr); /* If memory allocation
- error, free */
- return (-1); /* thestr, ptrarray, and
- return -1. */
- }
- strcpy(*ptrarray[index++], cptr);
- cptr = strtok (NULL, " ");
- }
- *ptrarray[index] = NULL; /* Terminate ptrarray with a NULL. */
- free(thestr); /* Free allocated memory. */
- /* Test the data. --- Delete the next four lines when
- debugged!! */
- printf("\nString list inside function...\n");
- for (index = 0; *ptrarray[index] != NULL; index++)
-
- printf("%u --> ***%s***\n", index, *ptrarray[index]);
- puts(" ");
-
- return (words); /* Return the number of pointers. */
- }
-
- /* **************************************************************
- Function: free_ptrarray
- Parameters: char ***ptrarray _> address of the pointer
- array.
- Description: Frees allocated memory for the pointer array
- allocated with the str_to_ptrarray function.
- Returns: Nothing.
- ************************************************************ */
- void free_ptrarray (char ***ptrarray)
- {
- unsigned count = 0; /* Declare and reset counter. */
- for (; *ptrarray[count] != NULL; count++)
- free(*ptrarray[count]); /* Free each string/word. */
- free(*ptrarray); /* Free the handle. */
- }
-
-
- Listing 3
- /* all_true.c */
- /* -----------*/
- */
- No matter what code the calling program returns, this
- program will always return 0 (success).
-
- Usage: all_true program args
- Example: all_true sleep 5
-
- */
-
- #include <stdio.h>
- #include <signal.h>
-
- #define RET_VALUE 0
-
- main(argc,argv)
- int argc;
- char **argv;
- {
- process(argc,argv);
- exit(RET_VALUE);
- }
-
- process(argc,argv)
- int argc;
- char **argv;
- {
- int pid;
-
- if ((pid = fork()) == -1)
- {
- perror("all_true");
- exit(l);
- }
-
-
- if (pid > 0 )
- {
- signal(SIGINT,SIG_IGN); /* Ignore interrupt key */
- while (wait( (int *) 0 ) == pid);
- return;
- }
-
- signal(SIGINT,SIG_DFL); /* Default interrupt key */
- argv++; /* Point to program argument */
- execvp(*argv, argv);
- perror("all_true");
- }
-
-
- Listing 4
- int DirSort(const void *pItem1, const void *pItem2)
- {
- CHAR ach1[MAXPATH], ach2[MAXPATH];
- register CHAR *psz;
-
- /*
- * Copy the path names to buffers
- */
- strcpy(ach1,((DIR_INFO *)pItem1)->achPathName);
- strcpy(ach2,((DIR_INFO *)pItem2)->achPathName);
-
- /*
- * Convert all backslashes (\) to hex 01s. Since the
- * backslash falls in the middle of the valid ASCII
- * codes for directory names, it could (and I have
- * seen it) cause the strcmp() to wrongly compare two
- * directories when they are different levels.
- */
- for (psz = ach1; *psz; psz++)
- if (*pse == '\\')
- *psz = 0x01;
- for (psz = ach2; *psz; psz++)
- if (*psz == '\\')
- *psz = 0x01;
-
- return(strcmp(ach1,ach2));
-
- }
-
-
- Listing 5
- /* from cuj */
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- int comp(const void *, const void *);
- unsigned char *list[] = {"cat", "car", "cab", "cap", "can"};
-
- main()
-
- {
- int x;
- qsort(list, 5, sizeof(unsigned char *), comp);
-
- for (x = 0; x < 5; x++)
- printf("%s\n", list[x]);
- return 0;
- }
-
- int comp(const void *a, const void *b)
- {
- return strcmp( *(char **)a, *(char **)b);
- }
-
-
- Listing 6
- // PROGRAM LISTING
-
- #include <stdlib.h> // necessary include files
- #include <stdio.h>
- #include <string.h>
-
- // *** DEFINE DATA STRUCTURES
- struct studentEntry
- {
- char name[40];
- float grade;
- } student [100]; // student is an array of studentEntry elements
-
- char name[100][40]; //name is an array of 40-character elements
- float grade[100]; //grade is an array of float elements
-
- // *** PROTOTYPE QSORT() COMPARE FUNCTIONS FOR EACH DATA TYPE
-
- int nameComp(const void *, const void *);
- int gradeComp(const void *, const void *);
- int structComp(const void *, const void *);
-
- // *** BEGIN MAIN PROGRAM
-
- int main( int argc, char *argv[] )
- {
- // read student file (however it must be done)
- FILE *studentFile;
- if(!(studentFile=fopen(argv[1],"rt")))
- {
- printf("error opening input student file\n");
- exit(1);
- }
-
- // read studentFile
- char line[100];
- char tmpName[40];
- float tmpGrade;
- for( int i=0; fgets(line, 100, studentFile); i++ )
- {
- sscanf(line,"%s %f", tmpName, &tmpGrade);
- grade[i]=student[i].grade=tmpGrade;
- strcpy(name[i],strcpy(student[i].name,tmpName));
- }
-
- fclose(studentFile);
-
-
- // *** SORT each list using its sort function
- qsort((void *)student,i,sizeof(studentEntry),structComp);
- qsort((void *)name,i,sizeof(char)*40,nameComp);
- qsort((void *)grade,i,sizeof(float),gradeComp);
-
- // print results
- for( int j=0; j<i; j++ )
- printf ("%20s %6.2f %20s %6.2f\n",
- student[j].name,student[j].grade,name[j],grade[j]);
- }
-
- // *** COMPARE FUNCTION DEFINITIONS
- // *** Notice the header. In the main body of the function the
- // *** arguments are cast to what they represent. The user must
- // *** do this1
-
- int name Comp(const void *a, const void *b)
- {
- return strcmp((char *)a,(char *)b);
- }
-
- int gradeComp(const void *a, const void *b)
- {
- if(*((float *)a)<*((float *)b)) return -1;
- if(*((float *)a)>*((float *)b)) return 1;
- else return 0;
- }
-
- int structComp(const void *a, const void *b)
- {
- if(((student Entry *)a>->grade<((studentEntry *)b)->grade)
- return -1;
- if(((studentEntry *)a)->grade>((studentEntry *)b)->grade)
- return 1;
- return 0;
- }
-
-
- Listing 7
- /* the following appears in the "globals"
- header file(s) */
- #ifdef IN_MAIN
- #define declare( obj, init ) obj = init
- #else
- #define declare( obj, init ) extern obj;
- #endif
-
- /* the declarations/definitions */
-
- declare( int i, 10 );
- declare( char str[], "An initialized string" );
- declare( int w[ 10 ][10 ], { 0 } );
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- On The Networks
-
-
- We Welcome A New Moderator
-
-
-
-
- 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).
-
-
- Last column, I mentioned that Brandon Allbery has gotten too busy to moderate
- comp.sources.misc. He asked for volunteers to do the job. The new moderator is
- now in place, and comp.sources.misc. is getting back to normal. Before I
- announce the new one, let me thank Brandon for his time as moderator. Being a
- moderator is at times a thankless job. Everyone complains when things go
- wrong, but few are there to thank the moderator when the network is flowing
- smoothly. It takes several hours per week to be a moderator of a group such as
- comp.sources.misc.
- The new moderator is Kent Landfield of Sterling Software, IMD. He can be
- reached at any of:
- kent@sparky.imd.sterling.com
- kent@uunet.uu.net
- sources-misc-request@uunet.uu.net
- Submissions for publication in comp.sources.misc should be sent to
- sources-misc@uunet.uu.net. Most of the major moderators maintain a mail
- clearinghouse at UUNET.
-
-
- New Sources In comp.sources.misc
-
-
- If you run a PC-compatible machine and keep different operating systems in
- different partitions, then you need bootmenu.bootmenu is a replacement for the
- primary boot sector of MS-DOS-compatible computers. This boot program lets you
- select the partition to be booted from a menu. Those with both UNIX and DOS on
- their disk can select which to boot. Two versions are provided. The first is a
- very small bootmenu that is compatible with SpeedStor-formatted disks but does
- not time out and automatically boot the active partition. The second is
- bootauto, which has a timeout to automatically boot the active partition if no
- entry is made. bootmenu also includes pfdisk v1.3 for installing the new boot
- sector without clobbering the current partition table. bootmenu was
- contributed for Volume 15, Issues 84 and 85 by Gordon Ross of the Mitre
- Corporation. He can be reached at <gwr@linus.mitre.org>.
- A start on a set of utilities to encode and decode Group III and Group IV fax
- images was contributed by Michael Marking <ipel!marking> for Volume 15, Issues
- 86-88. If you are playing with fax boards and fax images, these might be a
- base for your own software. Michael expects to extend these utilities to
- support these images imbedded in TIFF files. He also notes that these routes
- differ slightly from the ones in the CUJ article (CUJ July 1990, CUG volume
- 317).
- An interesting program was posted by Gene Olson, at <gene@zeno.mn.org>.
- Compact_sv is a System V program to compress large data streams. It
- demonstrates using shared memory as a buffer and having multiple tasks process
- the data in this buffer to speed throughput. The program is faster and
- produces smaller output than when compressing very large files (larger than
- 1Mb), but it requires more than 1Mb of memory to run. It implements data
- compression using a system very similar to V.42bis, entitled modified
- Lempel-Ziv-Welsh. The problem with this posting is its legal warning: "This
- program may be used only for research into data compression, as a study in
- program optimization, or as an example of System V interprocess communication.
- The author originally intended the program for practical day-to-day usage.
- However, in final preparation for program release, it was learned the program
- probably infringes one or more widely licensed patents in data compression.
- There is no choice but to release the program for research purposes only." I
- have never seen a program released with such a restriction before, but this
- one is worth looking at, if only to revive the concept of multiple tasks
- processing a buffer to increase throughput. Compact_sv is Volume 15 Issue 89.
- Enquire has been upgraded to v4.3 by Steven Pemberton <steven@cwi.nl> for
- Volume 15, Issue 95. Enquire determines many properties of a C compiler and
- its execution environment. These include minimum and maximum signed and
- unsigned char, int, long, float, and double, as well as other properties of
- floating point numbers. It can also produce ANSI C float.h and limits.h files
- based upon its tests. Other tests check for allocatable memory, bit order,
- casts, promotions, pointer properties, and accuracy of the math routines.
- Volume 15, Issue 98 was a patch to an item of interest to many. PCMAIL2, an
- MS-DOS PC program to exchange UUCP messages and electronic mail with UNIX
- hosts, has been patched to patchlevel 3. The full version was posted in
- January 1990. This patch fixes some problems with UUCP control messages and
- timing. They were submitted by Wietse Venema <wswietse@svbs01.bs.win.tue.nl>.
- If you are running a System V UNIX from a terminal that supports a status
- line, Charles Spell <cs00chs@unccvax.uncc.edu> has submitted a program written
- by John Ribera to monitor the system via that status line. Watch notifies the
- arrival of mail, the current time, and logons and logoffs from the system. The
- terminfo parameter tsl, to go to the status lin,e and fsl, to return from the
- status line and restore the cursor, must be in the terminal's terminfo
- descriptor. It also helps if ws#, the number of characters in the status line,
- is in the terminfo descriptor. Watch is Volume 15, Issue 99.
- I mentioned last column that, dmake, an enhanced make utility was updated to
- v3.6, and patch 1 was posted to comp.sources.bugs. Patch 1 has now appeared in
- comp.sources.misc as Volume 15, Issue 90. Because the bugs group is rarely
- archived, it is important that patches do eventually appear in the source
- group. Dennis Vadura <dvadura@watdragon.waterloo.edu> has also submitted a
- patch 2 to the bugs group, which should soon appear in the sources group.
- In last column's alt.sources preview section, I mentioned a CP/M emulator.
- This month another contributor has submitted a CP/M emulator in C complete
- with CP/M BIOS to comp.sources.misc. Nick Sayer <mrapple@quack.sac.ca.us>
- submitted upm, a UNIX CP/M as Volume 15, Issues 101 and 102. It emulates the
- Z-80 CPU and the CP/M emulator.
- Our new moderator took over with Volume 16. He started it out, with Issue 1
- being a simple utility. Submitted by Mark Allsop
- <mallsop@stimage.mqcc.mq.oz.au>, dosdu is a MS-DOS work-alike for the UNIX du
- (disk usage) command. It supports the major options of the UNIX version plus
- some for displaying sizes in Kb or Mb.
- A rather large contribution was Metalbase from Richard Jernigan
- <rpj@pygmy.rice.edu>. I normally don't mention shareware in this column, but
- there is no suggested contribution in this one, so I'll make an exception.
- Metalbase, submitted at v3.1, is a relational database system. It adds
- commands to C for adding, updating, deleting, and retrieving records from
- relations built with the utilities included in the package. The schemas are
- written in a pseudo-English. Originally written for Amigas, its been ported to
- UNIX System V. It was released as mbase in Volume 16, Issues 2-4.
- Jonas Yngvesson <jonas-y@isy.liu.se> and Inge Wallin <ingwa@isy.liu.se> have
- contributed a beta-test release of v2.0 of their SIPP, the simple polygon
- processor. SIPP is a library for creating three-dimensional scenes and
- rendering them using a scan-line z-buffer algorithm. The scene is built of
- objects that can be transformed with rotation, translation, and scaling.
- Objects form hierarchies, and hierarchies can have arbitrarily many subobjects
- and subsurfaces. The library has an internal database for the objects that are
- to be rendered. The library also provides three-dimensional texture mapping
- with automatic interpolation of texture coordinates. Simple anti-aliasing is
- performed via sampling, and multiple light sources and shading are supported.
- Images are produced in the portable pixmap format (ppm) which has been
- described in several prior columns. SIPP is Volume 16, Issues 5-10.
- A rather large patch appearing this time was patch 2 to gnuplot 2.0. The major
- additions are support for X11 Motif, new bit-mapped graphics routines, and new
- terminal drivers for vttek (VT like Tektronix emulators), HP LaserJet II,
- Kyocera Laser Printer, and SCO CGI graphics. This patch is applied to Volume
- 11, Issues 65-79 (gnuplot 2.0) and is Volume 16, Issues 11-17.
- For those sites wishing to run USENET Network News without installing the
- entire new package, bootstrap news contributed by Ronald Florence
- <ron@mlfarm.com> is for you. It allows a leaf node (you don't send news on to
- anyone else) to receive a limited news feed by spooling the articles to mail
- messages. It was released as Volume 16, Issue 18.
- If you're having problems debugging programs that use malloc/free for memory
- leaks, you need Marcus Ranum's <mjr@decuac.dec.com> mnemosyne from Volume 16,
- Issue 19. mnemosyne is a set of tools to find memory leaks and hog functions.
- It is a wrapper library for malloc/free and a #include file that intercepts
- those calls and sends the calls to the wrapper routines. This provides memory
- profiling and leak detection with no code changes save a single #include line.
- James Plank <jsp@princeton.edu> has contributed a graphing program that takes
- the description of the graph as input and outputs PostScript code. This
- PostScript code can be directly printed or imported into a text processor that
- allows for included PostScript images. Jgraph was released as Volume 16, Issue
- 20. Note, this was a very large issue (over 9,000 lines and 200,000
- characters). Usual issues are limited to less than 60,000 characters. Patch 1
- was issued as Volume 16, Issue 77.
- The largest posting this time was ECU, v3.00.00 and some patches to ECU. ECU,
- an extended cu, is an asynchronous communications program for SCO UNIX V/386
- and SCO Xenix V/386 or V/286. It incorporates a typical interactive
- communications capability with a rich procedure language and an extended set
- of interactive commands. It also supports several file transfer protocols. It
- requires SCO Xenix 2.3.1 or later or SCO UNIX 3.2.0. Submitted by Warren
- Tucker <n4hgf!wht>, ECU is Volume 16, Issues 25-59; patch 1 is Volume 16 Issue
- 60; and patch 2 is Volume 16, Issues 70 and 71. The manual for ECU was posted
- as Volume 16, Issues 22-24. An additional tool to take the log output from ECU
- 3 and convert it to Microsoft Excel spreadsheet format was posted as Volume
- 16, Issues 72 and 73.
- Ron Gallant has submitted a C++ implementation of the statistical methods used
- in his Non-linear Statistical Models book. Nlmdl can estimate univariate
- non-linear regression models by least squares, multivariate regression models
- by generalized least squares, simultaneous equations models by three-stage
- least squares, and dynamic simultaneous equations models by generalized method
- of moments. Errors can be heteroskedastic or serially correlated in any of
- these models. It was posted as Volume 16, Issues 63-68 with a patch in Issue
- 74.
- Patch 1 to the MS-DOS Shell written by Ian Stewartson <istewart@datlog.co.uk>
- (Volume 12, Issues 19-26) was released as Volume 16, Issue 78. This patch
- fixes some bugs and adds some features from the Korn and POSIX shells. In
- addition, support of use under OS/2 has been added.
-
-
- Comments Continue...
-
-
- Comments about the volume of postings in comp.sources.unix continue to appear,
- but the moderator, Rich Salz, keeps ignoring the flack and plodding along
- posting at his usual pace. I hope Rich keeps ignoring them, but I do wish he
- would post patches sooner. There are several new releases this time:
- If you're having difficulty making sense from the output of UNIX's ps command
- (process status), and if you are running a BSD derived UNIX (sorry, System V
- folks), Show Process Status (SPS) from J. Robert Ward of Olsen & Associates
- <robert@olsen.uu.ch> fits your needs. SPS displays the information sorted into
- parent/child order, with the fields displayed in English instead of hex where
- useful. In addition, it's faster than ps. Makefiles are provided for many BSD
- derived systems. SPS appeared as Volume 23, Issues 47-50.
- A line-oriented macro processor called LOME (Line Oriented Macro Expandor) was
- contributed by Darren New <new.ee.udel.edu> for Volume 23, Issues 51-59. LOME
- processes its input file one line at a time applying the macros until no more
- matches can be performed. The macros can also reference other files allowing
- input and output to multiple source or output files.
- Those long-time subscribers to CUJ may remember my article on source-code
- librarians. One of the librarians I described was RCS. In Volume 23, Issue 75
- Jason Winters <grinch!jason> submitted a front-end to RCS's ci/co (check in
- and check out) entitled cio. The wrapper allows for recursively checking in or
- out of directory structures and limiting the command to ASCII files only, thus
- enabling the program to be run on directories with both source and binaries.
- Running multiple printers from the same queue has been easy in the System V
- line printer spooler for a long time using printer classes. However, the BSD
- spooler has not supported this same feature. Matt Robinson
- <yakker@ucrmath.ucr.edu> submitted mlpd for Volume 23, Issue 76. mlpd supports
- using a single printer queue for a set of multiple printers. The idea is
- similar to that of a common line at a bank. The next job gets the first
- available printer from the set. It's simple, but not optimal. More optimal
- would be to place short jobs on one printer with a higher priority. Longer
- jobs and the overflow of short jobs get distributed among the rest of the set
- (if optimizing throughput).
- Also for sites running BSD derived UNIX systems is xmodem3.9. This version of
- XMODEM supports the BSD flavor of terminals and terminal programs. It adds
- running through tip, delayed startup, status messages, turning off EOT
- verification, aborted transfers, and YMODEM-G support. Older xmodem3 versions
- for BSD already supported XMODEM/CRC, XMODEM Block, and YMODEM batch. This
- version was contributed by Steve Grandi <grandi@noao.edu> for Volume 23,
- Issues 77-79. This does cause a slight conflict in version identification. For
- the System V derived systems, a similar program called UMODEM is available,
- currently in the 4.x version range.
- Another news reader variant was posted recently. One of the older news readers
- is Larry Wall's rn. New from Wayne Davison <davison@sri.com> is Threaded RN
- (trn). It uses the References line to order the discussions in a natural,
- reply-ordered sequence called threads. Having the replies associated with
- their parent articles makes the following discussion easier. trn also has a
- visual representation of the current thread in the upper right corner of the
- header. A thread browser/selector has been added to make long threads easier
- to handle. trn uses a thread database, created by a separately run database
- manager (included). The space taken up by the database is an additional 5
- percent of the space already used in the news spool area. trn requires rn
- patchlevel 47 or greater. The additions are all marked for conditional
- compilation. The same source can then be used to compile trn or rn. trn was
- posted twice, the first posting had problems with the packaging so it was
- reposted again with patch 1 applied. The revised patch was Volume 23, Issues
- 60-73. Also posted as part of trn was unipatch format. Unipatch makes patches
- that are smaller than context differences but contain the same information for
- the patching program. It does this by merging the add and deletes into one
- list instead of showing the before and after lines as two lists.
- The last large posting in comp.sources.unix this time is an implementation of
- ABC, a new interactive programming language. The source provides versions for
- UNIX, the Atari ST, the Macintosh, and MS-DOS. ABC is an imperative language
- originally designed as a replacement for BASIC. In addition to being
- interactive and easy to learn, ABC is structured and high-level. It is
- suitable for general everyday programming, the same as you would use BASIC,
- Pascal, or AWK. It is not a systems programming language but it is an
- excellent teaching language. It is interactive and supports prototyping. It is
- a compact language with the source for most programs being about a quarter to
- a fifth the size of the equivalent Pascal program. Submitted by Steven
- Pemberton <steven@cwi.nl>, ABC is Volume 23, Issues 80-106, with Issue 105
- being a correction for two files that some systems truncated due to line
- length restrictions, and Issue 106 being patch 1. Patch 2 was issued as a
- second Issue 106. (Seems Rich got a bit confused with all the problems that
- occurred in the posting of ABC.) Patch 3 was Issue 108. Since there was no
- 107, patch 2 should have been Issue 107.
-
-
-
- reve Continues To Evolve
-
-
- Last column I mentioned an Othello-like game, reve, which was upgraded to
- v1.1. It was submitted by Rich Burridge <rburridge@sun.com> and Yves Gallot
- <galloty@cernvax.cern.ch> for Volume 11, Issues 52-58 with patch 1 in Volume
- 11, Issues 61-64. This time patch 2 was released as Volume 11, Issues 97-99.
- Patch 3 was released as Volume 12, Issues 1-9. Patch 4 followed immediately as
- Volume 12, Issues 10-13 and a small follow-up patch 5 as Issue 14. There are
- too many additions and changes to mention here. Most changes are due to making
- reve run on many types of computers and variants of display and operating
- systems.
- Other patches released this period include patch 1 to bt, the Broken Throne
- multiplayer real-time conquest game from Tom Boutell
- <boutell@freezer.it.udel.edu>. Patch 1 was issued as Volume 11, Issue 77. A
- Sunview clear-the-bricks game, blockbuster (Volume 11, Issues 18-20) had patch
- 1 issued by Eric Van Gestel <ericvg%BLEKUL60.BITNET@cunyvm.cuny.edu> as Volume
- 11, Issue 95. Lastly on the patch front, sdi, a Sunview missile-command game,
- had patch 1 issued. sdi is Volume 1, Issues 54-59 and the patch from Bill
- Randle <billr@saab.cna.tek.com> came from a bug fix submitted from Brian
- Nakata. The patch was released as Volume 11, Issue 96.
- A port of the X11 game Boulder Dash to MS-DOS PCs using VGA display adapters
- was posted as Volume 11, Issues 81-83. It was submitted by Herve Soulard
- <soulard@fantasio.inria.fr>. The original version for X11 windowing systems
- was written by Jeroen Houttuin <houttuin@ks.id.ethz.ch>. Included are three
- programs, XBD, the game itself, XBDE, a level editor for XBD, and BITMAP, a
- bitmap editor for XBD.
- The largest posting in the games group was larn, a dungeon type adventure game
- for UNIX, VMS, MS-DOS, or any termcap supporting system. larn, now at v12.2,
- is an enhancement of the original larn (written by Noah Morgan) by Kevin
- Routley <routley@tle.enet.dec.com>. This version differs from ularn in Volume
- 7 of the game's archives and is closer to the original in look and feel than
- that version. larn is similar in concept to hack, rogue, or moria, but with a
- different feel and winning criteria. larn was posted in 11 parts as Volume 11,
- Issues 84-94 with patch 1 as Volume 12, Issue 16.
-
-
- Previews From alt.sources
-
-
- There was an explosion of postings in alt.sources. So many, that I can only
- touch on some of them. I hope the better ones will be checked out and posted
- to the mainstream source groups in the future.
- remind, v2.2 was posted in five parts on Nov. 16, 1990 by David Skoll
- <dfs@doe.carleton.ca>. remind is a program to prompt about upcoming events. It
- can mail reminders, prompt them on the screen, and now produce an annotated
- calendar for printout. New in 2.2 is the ability to read and handle standard
- UNIX calendar files. remind 2.2, patch 3 was posted on Nov. 28, 1990, patch 4
- on Nov. 29, 1990 and patch 5 on December 3, 1990. I did not see patches 1 or 2
- go by, but they may have already been applied to the original posting. Since
- postings in alt.sources are uncontrolled, it's hard to tell if something is
- missing.
- A set of routines to extend printf were posted by D. Richard Hipp
- <drh@duke.cs.duke.edu> on Nov. 29, 1990. The new variations include mprintf
- that uses malloc to acquire space to place its output, snprintf, a version
- that limits its length to a set number of characters a la strncpy, xprintf
- that calls a function to dispose of its output, and nprintf, which only
- returns the number of characters that would have been output. The other
- enhancement is that new conversion format letters can be added at runtime. An
- additional enhancement for centered output was posted on Nov. 30, 1990.
- Need a cross-assembler? Mark Zenier <ssc!markz> posted his frankenstein
- cross-assembler. It supports 13 flavors of assemblers (1804, 2650, 6301, 6502,
- 6805, 6809, 6811, tms7000, 8048, 8051, 8096, z8 and z80). No macros,
- relocatable linkers, fancy print controls, or structured control statements,
- but as he states, "It's a start." It was posted on Dec. 4-7, 1990 in many
- parts, about 1.2Mb in all.
- The vi editor clone, Elvis, was upgraded to 1.4 on Dec. 5, 1990. A nine-part
- posting by Steve Kirkendall <kirkendall@eecs.cs.pdx.edu>, Elvis now runs under
- UNIX, MS-DOS, OS-9, and Coherent. New in 1.4 is an alias program to allow for
- smaller disk usage on systems that do not support links. This allows a small
- wrapper for the view, ex, and vi varients of the Elvis executable. Also new
- are line number support for the :e command, appending to a named cut buffer,
- limited macro facility, addition of the :abbr command, adding a delta to vi
- mode searches, and several new :set options including showmatch, autoprint,
- and edcompatible. Also added are a new set of commands to scan the error
- messages issued by the compiler. These are :make and :cc. Work is in progress
- for v1.5.
- A seven-part posting of a work-alike of the Korn shell by Eric Gisin was
- posted by netcom!netnews on Dec. 12, 1990. I only mention it because a large
- number of messages have recently been asking about a public domain version of
- the Korn shell. Apparently the author is no longer reachable, so this makes a
- starting point. It supports most of the older Korn shell, but only has EMACS
- line editing. The other line-editing modes were not present.
- Tom Horsley <tom@ssd.csd.harris.com> posted mkid, an identifier cross
- reference tool on Dec. 12, 1990 in an eleven-part posting. He is looking for
- comments and corrections. This is a variation on the mkid package from 1987
- written by Greg McGary. He has made several additions and corrections, and is
- asking others to send him their patches to the original program. He will
- attempt to merge all of them and issue a new version to comp.sources.unix.
- Among the shell variants, the Z shell (zsh) was posted in eight parts by Paul
- John Falstad <pfalstad@phoenix.princeton.edu> on Dec. 14, 1990. The Z shell is
- similar to ksh and tcsh. It is designed to be an interactive shell as well as
- a batch command language. Concepts were borrowed heavily from ksh, bash, tcsh,
- sh, and csh.zsh is a work in progress, but it has a manual page and make
- files.
- If you wanted to try one of the programs I described that required BSD type
- sockets, but you were on a machine that does not support networking,
- pgd@bbt.se (I never did find his name on the posting.) posted a socket
- emulation library for System V on Dec. 19, 1990. It uses named pipes and file
- locks. Of course, without networking you are limited to the local machine, but
- it works well enough to put up X-windows on a Xenix machine without networking
- or streams.
- On Dec. 20, 1990 James R. Purdon, III <purdon@athena.mit.edu> posted slugnet
- in six parts. slugnet is a multi-user interactive conferencing facility. It
- does require sockets and runs in a client/server configuration. It allows
- multiple site conferences as well as multiple conferences.
- For those sites that need an ndbm, Ozan Yigit <oz@nexus.yorku.ca> has provided
- sdbm. The Substitute DBM library was posted on Dec. 15, 1990. It provides a
- complete, portable replacement for the ndbm system. ndbm uses a different
- algorithm so the data files are not compatible.
- Another menuing system was posted by Paul Condie <pcbox!pjc> on Dec. 26, 1990
- in 14 parts. A simple utility that allows for adding menus to UNIX. Menus
- supports sub-menus and processes. Nesting does not increase startup time or
- memory usage. Menus can also be data entry screens allowing for choices and
- responses to be controlled.
- The moderator of comp.sources.unix even made a posting to alt.sources. Rich
- Salz <rsalz@bbn.com> submitted his pattern matching subroutines wildmat.c on
- Jan. 3, 1991. It can match arbitrary patterns using wild cards. It is 8-bit
- clean. An additional correction and comment was made on Jan. 4, 1991 by Lars
- Henrik Mathiesen <thorinn@diku.dk> in regard to the abort code speedup.
- William E. Davidsen, Jr. <davidsen@crdos1.crd.ge.com> posted a version of
- LHARC that he modified to run cleanly on a large number of UNIX systems. His
- two-part posting on Dec. 6, 1990 is version 0.03 beta.
- Those longing for X-11 Rel 4 and still running SCO Xenix 386 need only see the
- 12-part posting on Jan. 7, 1991 by Chain Lee <chain@paul.rutgers.edu>. He
- posted a large set of patches to adapt the source to Xenix. Of course you
- still need the X11R4 source and its 18 patches first. That alone takes four
- magnetic tapes.
- All UNIX system administrators need to acquire COPS. The latest version, 1.02,
- was posted in nine parts on Jan. 8, 1991 by Dan Farmer <df@sei.cmu.edu>. Dan
- is part of the Computer Emergency Response Team (CERT). This package, which he
- started before working for CERT, is based on his suspicion that most security
- problems are caused by trivial misconfigurations. COPS checks out your UNIX
- system for security holes and reports its findings. It is up to the system
- administrator to correct those holes. COPS is a tool to assist in checking for
- potential security problems, not a crutch. Reliance on it to find all problems
- is asking for trouble. But it is easy to install and run, and it should be run
- regularly.
- A cron work-alike including the at facility (batch jobs) was posted on Jan.
- 13, 1991 by Donald Lashomb <uncle!donlash> in four parts. It supports cron,
- crontab, at, and batch commands. It permits names as well as numbers for the
- date fields in the cron tables, enforces the real uid for security, and
- includes a new cronjob. The last is like the batch at command, but works
- repetitively like a crontab. It is mostly compatible with the standard System
- V versions.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- C Communications Toolkit
-
-
- Victor R. Volkman
-
-
- Victor R. Volkman received a bachelor's degree in computer science from
- Michigan Technological University. He is a software engineer at Cimage
- Corporation, Ann Arbor, Michigan, and can be reached at the HAL 9000 BBS,
- 313-663-4173, 1200/2400/9600 baud or via Usenet as vrv@cimage.com.
-
-
- The C Communications Toolkit (hereafter CCT or toolkit) from Magna Carta
- Software (Garland, TX) is a comprehensive package providing everything from
- low-level UART register manipulation to batch file-transfer protocols. It also
- supplies data translation and ANSI terminal emulation facilities. The CCT is
- shipped with libraries compiled for Turbo C, Watcom C, Microsoft C, and Mix
- Power C. Each of the libraries includes versions built for the small, medium,
- and large memory models. The toolkit includes all the source code you need to
- port to an unsupported memory model or compiler. It retails for $150. Magna
- Carta backs its product with a 30-day money-back guarantee.
- I received version 1.00B of the toolkit, which was dated 06/09/90. The entire
- install set was squeezed onto three 5¼ 360K disks using LZH compression.
- Installation consists of uncompressing the .LIB, .C, and .H files into the
- directories of your choice. The tookit should work fine with any PC (from 8088
- to 80486) on any version of DOS (3.0 or later).
- The CCT supports a variety of communications hardware. The primary emphasis is
- on the INS8250 family of Universal Asynchronous Receiver/Transmitters (UART)
- chips. The INS8250 family, which has been the PC standard for almost a decade,
- includes NS16450 and NS16550A compatible chips as well. Special support is
- provided for the FIFO buffer and interrupt threshold features of the NS16550A.
- This toolkit also supports the Zilog Z-80 SIO communications chip. The SIO
- provides both synchronous and asynchronous communications, but it is normally
- found only on 3270 terminal emulation adapters. Because there is no standard
- hardware interface for the SIO on the PC, the CCT implementation is only
- guaranteed to work with AST's 3270 adapters.
- The toolkit also supports the Intel Connection Coprocessor using the
- Communication Applications Standard (CAS). The CAS interface is a high-level
- mechanism for scheduling file and facsimile transmissions.
-
-
- Initializing Communications
-
-
- The CCT supports both polled and interrupt-driven communications for receiving
- and transmitting data. The init_port function can initialize each of these
- four cases. Once a port is initialized, it is available to begin transmitting
- and receiving characters. An example invocation is:
- init_port(&port1, COM1, 1200L, DATABITS8,
- PARITY_NONE, STOPBITS1, txbuf, rxbuf);
- The first argument is a pointer to a mostly uninitialized COMM_PORT data
- structure. The second argument is the base address of the serial port to be
- initialized. The next four parameters set up the basic configuration of the
- serial link (1,200 baud, 8N1). The last two arguments specify optional
- transmit and receive buffering.
- If the rxbuf argument is not a null pointer, then init_port sets up an
- interrupt-handler for receiving characters into the buffer. If the txbuf
- argument is not a null pointer, then the function sets up a similar handler
- for transmitting characters. If both rxbuf and txbuf are null, then all I/O
- takes place in polled mode.
- The default transmitter and receiver buffer sizes are set to 1,024 bytes and
- 2,048 bytes respectively. If these are inappropriate, then you must either
- change the #defines in COMM.H or call the lower-level c_open function. For
- c_open to work correctly, you must initialize several more fields in the
- COMM_PORT data structure in advance.
-
-
- Ports And Modems
-
-
- The CCT keeps track of everything related to a given communications session in
- a COMM_PORT data structure. This all-encompassing data structure contains more
- than 150 members to describe the state of the session. COMM_PORT conforms to
- the guidelines set forth by Campbell (1987) for writing UART-independent
- communications programs. Both the COMM_PORT and MODEM data structures closely
- parallel Campbell's definitions. The CCT does not use Campbell's "virtual
- UART" model, but it does capably isolate UART-specific registers by
- incorporating a union pointer in COMM_PORT. The utype member of COMM_PORT tags
- the UART union being used. A UART union appears as:
- typedef union uart {
- UART8250 u8250; /* Intel */
- UARTZ80SIO uz80sio; /* Zilog */
- } UART;
- The other members of COMM_PORT define parameters for buffers, flow control,
- RS-232C inputs and outputs, line characteristics, receiver and transmitter
- data translation, and physical port characteristics. In addition to the
- session-specific parameters, COMM_PORT also includes more than two dozen
- function pointers. Last, COMM_PORT contains a pointer to a MODEM structure.
- The COMM_PORT function pointers designate Interrupt Service Routines (ISRs),
- low-level UART-specific services, and callback functions. The ISR function
- pointers in the COMM_PORT structure keep track of which ISRs belong to a given
- port. Generally, each communications port on the PC requires a dedicated
- Interrupt Request (IRQ) line. The PC architecture dictates that each IRQ line
- must have its own ISRs. The function pointers for low-level UART-specific
- services transmit and receive characters without requiring every function to
- be intimately familar with how UART works. The callback function pointers
- enable the CCT functions to better serve your application's environment. For
- example, you can transparently send a copy of the communications stream to
- your printer by pointing the p_out member to your own printer echoing
- function.
- As mentioned earlier, the COMM_PORT data structure also contains a pointer to
- a MODEM data structure. The MODEM data structure holds all of the data needed
- to initialize a modem before connection. Nearly 100 initialization parameters
- can be set if desired. MODEM is composed almost entirely of Hayes Smartmodem
- command strings. For example, the dial_cmd member typically contains the ATDT
- dialing prefix. Nearly all modems manufactured within the last five years
- understand at least a subset of these commands.
- The CCT provides several functions to take advantage of the MODEM information.
- Each of these functions locates a MODEM structure via a COMM_PORT. The
- modem_init function sends the initialization strings in MODEM. The modem_dial
- function takes a telephone number string and performs the dialing for you. The
- modem_hangup function hangs up the phone immediately. The general purpose
- modem_scmd function sends a command to the modem and returns a pointer to the
- reply buffer. For example, you use the following sequence to turn off the
- modem speaker:
- strcpy(my_port->modem.speaker, "MO");
- reply = modem_scmd(my_port,
- my_port->modem.speaker, BUF_SIZE);
- if (strcmp(reply,"OK") != 0)
- printf("Error: modem not responding!\n");
-
-
- Data Translation
-
-
- The toolkit provides extensive support for translation on both received and
- transmitted data. The CCT data translation functions govern character echoing,
- newline handling, programmable delays, and flow control issues. The parameters
- for data translation can be changed at any time via the set_rx_xlat and
- set_tx_xlat functions. Both of these functions take three arguments. The first
- is the COMM_PORT to be affected. The last two tell which translation item to
- set and to which value to set it. For example, to enable printer echoing you
- would call the function:
- set_rx_xlat(&myport, PRINTER_ECHO, ON);
- The data translation functions enable you to control when and where characters
- will be echoed. The LOCAL_ECHO, REMOTE_ECHO, PRINTER_ECHO, and
- CAPTURE_BUFFER_ECHO flags control echoing. The LOCAL_ECHO flag tells whether
- characters should be displayed as they are read or written. On receive, the
- REMOTE_ECHO flag tells whether received characters should be echoed back. On
- transmit, REMOTE_ECHO tells whether you should wait for a return echo before
- transmitting the next character. The PRINTER_ECHO flag allows received and/or
- transmitted characters to be logged to a printer. Last, CAPTURE_BUFFER_ECHO
- permits received or transmitted characters to be simultaneously copied to a
- memory buffer.
- The CCT data translation functions also give you extensive control over
- newline handling. Special newline handling is often needed for communicating
- with mainframe hosts and UNIX workstations. As with other translations,
- newline handling can be independently specified for receiving and
- transmitting. The nine different options available include Linefeed to
- Carriage-Return (LF2CR) and Carriage-Return Linefeed to Linefeed (CRLF2LF).
- Although programmable delays are not strictly a data translation, the same CCT
- functions enable them as well. The INTERBYTE_DELAY and TRAILINGBYTE_DELAY
- flags control delays. On receive, INTERBYTE_DELAY allows you to specify a
- timeout value to wait for each incoming character. On transmit,
- INTERBYTE_DELAY specifies a pause between each outgoing character. This is
- handy for communications sessions with mainframe hosts, which have notoriously
- poor receive buffering. TRAILINGBYTE_DELAY allows for extra time after the
- newline is transmitted or received.
- Last, the translation functions also set up the flow control parameters. Flow
- control prevents overrunning the receive buffer when data is coming in faster
- than it can be processed. The C Communications Toolkit supports RTS/CTS,
- DTR/DSR, and XON/XOFF handshaking. For XON/XOFF control, you must also
- designate the characters used to represent these conditions. Some of the flow
- control techniques can be combined.
-
-
- File Transfers
-
-
-
- The CCT has high-level functions to send and receive files using several
- popular protocols. Specifically, CCT supports the XModem, XModem-CRC,
- XModem-1K, YModem, Y- Modem-G, and Kermit protocols. With the CCT, receiving a
- file can be as simple as calling freceive with the appropriate parameters. A
- sample invocation of freceive is:
- ret = freceive(&myport, &xinfo, protocol,
- 10*1024L, transfer_progress);
- The first argument to freceive is the COMM_PORT structure pointer. The next is
- a pointer to an unitialized XFER structure. An XFER structure consists of
- about two dozen members that describe every aspect of the file transfer. The
- third argument to freceive specifies the suggested protocol to use. Under
- certain circumstances, freceive can change the protocol in midstream if it
- discovers that the sender is using a different protocol. The next argument
- tells how large the receive buffer should be. The receive buffer is
- dynamically allocated within freceive and will be automatically reduced if
- insufficient heap space is available. The last parameter is a pointer to a
- callback function for progress reports. The progress report function must
- receive parameters for the COMM_PORT, status, and bytes received so far.
- Typically, a progress report function formats and displays status messages in
- a screen window.
- With the CCT, sending a file is only slightly more complicated than receiving
- one. The primary difference is that files must be enqueued before sending
- them, even if only one file is going to be sent. The fqueue function takes a
- pointer to an XFER structure and a string containing the filename to enqueue.
- For multiple-file (a.k.a. batch) protocols, each invocation of fqueue adds
- another file to the list. The fsend function does the actual work of sending
- the file:
- fqueue(&xinfo, "HAL_9000.ZIP");
- fqueue (&xinfo, "TOOLKIT.DOC");
- ret = fsend(&myport, &xinfo, YMODEM,
- transfer_progress);
- The arguments to fsend are similar to those described for freceive, except
- fsend uses a smaller fixed-size buffer for transmission. fsend can also change
- the protocol in midstream to any XModem or YModem variant if it detects the
- receiver using a different protocol.
- The CCT makes the normally unwieldly Kermit protocol easy to use. To send or
- receive via Kermit, you need only add a call to init_kermit just before the
- actual freceive or fsend. init_kermit initializes a KERMIT_PARMS structure
- containing about 40 members. By modifying the KERMIT_PARMS structure, you can
- fine-tune the Kermit protocol parameters.
- Although you can implement file transfer protocols that are not supported by
- CCT, it might be a daunting task to do so. The source code for the supplied
- protocols is heavily tailored toward the eccentricities of the XModem
- variants. However, you could still use the library source as a model for new
- protocol development.
-
-
- Terminal Emulation
-
-
- The CCT also provides VT-52/100 and ANSI X3.64 terminal protocol emulation.
- These two emulations together account for nearly all character-based terminals
- used today. The init_term function selects and installs terminal emulation.
- init_term requires three arguments. The two supported invocations of init_term
- are:
- t = init_term(&myport,
- vt100_write, vt100_conoutc);
- t = init_term(&myport,
- ibmansi_write, ibmansi_conoutc);
- The first argument is the COMM_PORT for which terminal emulation will be
- enabled. The next argument is a pointer to the function that will process
- serial output. The final argument is a pointer to the function that will
- handle screen output. Although the documentation makes much ado about how new
- terminal drivers can be added in a "cookbook fashion," there are barely two
- pages devoted to writing an alternate terminal emulation.
- Unfortunately, the CCT terminal emulations use the BIOS video INT 10h for all
- output. The INT 10h interface is an order of magnitude slower than writing
- directly to memory, which will undoubtedly reduce throughput on high speed
- (9,600 baud or greater) links.
-
-
- Performance
-
-
- The best measure of a communications library is how well it performs versus
- other applications. I compared the example programs supplied with the CCT
- against TTYTALK and ProcommPlus v1.0. I developed TTYTALK, a complete terminal
- program for the IBM PC written with C, in 1988 (see bibliography). TTYTALK
- supports a subset of the command language found in the popular CROSSTALK XVI
- package by DCA. TTYTALK features input/output filtering, data translation, and
- send and capture of ASCII files. The CCT example program #8 most closely
- matches the capabilities of TTYTALK. My comparison indicated that the size of
- the source files for TTYTALK and CCT example #8 were roughly equal (23K vs.
- 18K). When built with Microsoft C 5.1 small model, the executable size for CCT
- example #8 was only about 32K larger than TTYTALK.EXE (which was 17K). See
- Figure 1. Since the entire CCT large model library is less than 100K, the
- library imposes little overhead.
- For the second stage of performance evaluation, I benchmarked the CCT examples
- #9 and #10 against the ProcommPlus v1.0. I designed this test to match their
- file transfer capabilities on XModem-1K at both 2,400 and 9,600 baud (v.32). I
- ran all tests on a 25MHz 80386 computer with a NS16550A UART chip and a Hayes
- ULTRA 96 v.32 modem. The programs were run in a DESQview v2.26 DOS window with
- 340K RAM available. On the other end of the communications link, I used a
- Telebit T2500 v.32 modem in conjunction with a 10MHz 80286 computer. The
- connections uniformly used the LAP-M link protocol and v.42bis data
- compression to transmit .ZIP files. The host BBS software used was PCBoard
- v14.5/E3 with internal protocols. The PCBoard host software reported the
- effective file transfer rates. My results show the "best case" transfer rates
- that I obtained after at least four trials.
- The results of testing at 2,400 baud showed no significant differences between
- ProcommPlus and the CCT (see Figure 2). The file transfer efficiency was
- within expected norms for the X-Modem-1K protocol. However, at 9,600 baud I
- had some problems with the CCT application. The XModem-1K sent reported errors
- on several occasions and aborted the transfer. When the file sends were
- successful, the CCT application performed at the same level as ProcommPlus
- (see Figure 3). Although I had no difficulty receiving files with the CCT at
- 9,600 baud, the transfer rates were consistently about 100 characters per
- second (cps) behind ProcommPlus. The XModem family of protocols often has
- difficulty at high speeds. For this reason, most highspeed modem users prefer
- Zmodem when at 9,600 baud and above.
-
-
- Documentation
-
-
- The CCT documentation consists of a single 600-page paperback reference and
- tutorial book. Approximately half the book is devoted to a tutorial of basic
- communications concepts including transmission modes, the RS-232 standard,
- UART register sets, the Hayes standard "AT" command set, file transfer
- protocols, and terminal emulation. The rest of the manual describes header
- files and gives an alphabetical list of functions, parameters, and return
- codes. The manual could easily be improved in two regards. First, it lacks a
- glossary of communications terminology. Second, the index should contain
- entries for all of the CCT functions.
- The CCT takes a gentle approach to demonstrating typical communications
- applications and how they can be solved. The tutorial book presents a series
- of 17 complete mini-applications in order of increasing complexity from a
- polling dumb-terminal to a full transmit/receive interrupt-driven terminal.
- Along the way, the book shows capabilities such as terminal emulation and file
- transfer protocols. None of the tutorial programs demonstrates how to use the
- advanced features of the NS16550A UART.
- The documentation discusses the Hayes command set and individually covers
- specifics of the Smartmodem 300, 1,200, and 2,400 baud models. The Hayes
- V-series is also mentioned, although the Hayes ULTRA 96 v.32 modem is not
- covered. The manual also ignores the differences between the Smartmodem 1200
- and 1200EF (Extended Features) products. Although the CCT contains
- vendor-specific support for Telebit series modems, the manual does not list
- the Telebit extensions to the Hayes command set.
- The manual precisely describes the XModem file transfer protocol. The authors
- correctly indicate all of XModem's faults, variants, and workarounds. All of
- the popular Xmodem extensions including XModem-CRC, XModem-1K, YModem, and
- YModem-G protocols are summarized. The Kermit file transfer protocol is
- treated the same as the Xmodem protocol. The documentation also describes the
- Kermit 8th-bit quoting and Run-Length Encoding (RLE) extensions that the CCT
- offers.
- The function reference section groups the function calls by category. The
- functions are then presented alphabetically within each category. The notes
- for each function indicate its purposes, arguments, and return values.
- Additionally, the notes mention the header file in which it is declared, any
- hardware limitations it might have, and all of the lower-level functions that
- it might call.
- One area of communications ignored by the tutorial programs and text is
- development of host (e.g. BBS) software with the toolkit. Although many of the
- issues are the same for both host and terminal modes, a novice could benefit
- from such a discussion. Topics for host support would include ring detection
- and answering, loss of carrier detection, line settings, and Hayes register
- settings for the host.
- The manual does contain some typographical errors, but these are easily
- overlooked given the clarity of the explanations. There are also a few
- references to nonexistent figures and tables.
-
-
- Support
-
-
- Magna Carta Software provides technical support via telephone and Bulletin
- Board System (BBS). However, you must mail in the registration form to obtain
- a serial number before you can obtain any technical support. The Magna Carta
- BBS consists of a single 2,400 baud modem line running OPUS software. The BBS
- is frequented by Andrew Chalk, the principal developer and president of Magna
- Carta Software. In addition to communication utilities and specifications, the
- BBS also has the latest beta test version of the CCT. During my product
- testing, I downloaded CCT v1.00D from the Magna Carta BBS. Most support
- questions posted on the BBS are answered within 24 hours.
- The back cover of the CCT manual and all of the advertising indicates that
- several intelligent multi-port serial boards are supported, including models
- from AST, CommTech, Digiboard, and Arnet. An intelligent multi-port serial
- board usually includes an onboard CPU, such as an Intel 80186. These boards
- typically cost more than the motherboard on your computer. A multi-port board
- may contain 4, 8, 16, or even 32 serial ports. These adapters often include
- onboard EPROM and RAM areas. The adapter RAM may be either privately
- transferred via DMA or mapped through a window in high DOS memory (like video
- RAM).
- A thorough search of the documentation and source code revealed no references
- to any of these multi-port boards. After scouring the Magna Carta BBS, I
- discovered supplemental source code for the CommTech and Digiboard products
- which had been omitted from the CCT. I asked Andrew Chalk about this apparent
- support discrepancy. He said the CommTech and Digiboard drivers would be
- distributed in the future starting with CCT v1.01. Chalk also said that
- drivers for Star Gate ACL/II and the AST CC-832 adapters were currently being
- developed. The release dates for these drivers have not yet been announced at
- the time of this writing.
- Support for the CommTech FASTCOMM4 board consists of 200 lines of C allowing
- it to be addressed as an array of serial ports. Support for the DigiBoard
- DigiCHANNEL COM/4i and COM/8i is much more extensive than for the CommTech
- board. The DigiCHANNEL driver provides methods to download communications
- functions and execute them on the adapter's own CPU. Special board-specific
- functions manipulate the command and data queues for each port. The CCT
- functions modified for the DigiCHANNEL board all have an xi_ prefix added to
- them. For example, the set_speed function is renamed xi_set_speed for the
- DigiCHANNEL. This function naming convention would seem to make it difficult
- for an application programmer to maintain a single set of device-independent
- source modules. An application programmer would most likely need to resort to
- #ifdefs to work around this naming convention.
- If you are using a multi-port board that the CCT does not currently support,
- Magna Carta Software will consider adding support under an exchange agreement.
- If you are willing to loan the board to Magna Carta for 30 days, they will
- consider writing the drivers for you. In return, Magna Carta retains the
- rights to distribute the drivers in subsequent releases of CCT.
- Both data structures and functions of the CCT have been revised since release
- 1.00 in May 1990. The revisions leading up to v1.00D require changes at the
- source code level to applications written with the initial version of CCT.
- These changes are fully documented in the release notes and are mainly
- oriented toward removing hardware-dependent code at the UART level -- an
- admirable goal. The source-level interface should remain stable in future
- releases.
-
-
-
- The Competition
-
-
- Magna Carta's CCT compares favorably in both price and functionality against
- competing DOS communications libraries. Some other packages do not support the
- Z-80 SIO USART or VT-100 emulation. Most DOS communications libraries offer
- support for the same file-transfer protocols, flow-control mechanisms, UARTS,
- and multi-port serial boards. In addition, many libraries are including full
- library source code at no extra charge.
- With a list price of $150, the C Communications Toolkit is the least expensive
- of the libraries I have seen. Other packages, such as the C Asynch Manager by
- Blaise Computing ($189), C Asynch Library by SilverWare ($250), and Essential
- Communications by South Mountain Software ($329), provide similar
- functionality but with a higher cost.
- The CCT file transfer protocol suite is only missing ZModem capability.
- Z-Modem is by far the most efficient file transfer protocol available. ZModem
- offers CRC-32 error detection, dynamically negotiated block sizes, and the
- ability to restart aborted file transfers (Forsberg 1990). The Solid Link
- communications library by Solid Software ($199) is the only competitor that
- includes the ZModem protocol. However, the source code for Solid Link is
- optional and costs significantly more than the package itself.
-
-
- Conclusion
-
-
- The C Communications Toolkit is one of the most comprehensive packages I've
- come across. The CCT price compares well with other packages, and the tutorial
- text and example programs are sufficient for even a novice C programmer. The
- function set is adequate for developing a commercial quality terminal program
- and includes full library source. In file-transfer protocols, the CCT performs
- as well as existing communications programs such as ProcommPlus. If you plan
- to use an intelligent multi-port serial board, be sure to contact the vendor
- to determine exactly what level of support is available. For many
- communication applications, the C Communications Toolkit may be all the help
- you need.
-
-
- Bibliography
-
-
- Campbell, Joe. C Programmer's Guide to Serial Communications. Indianapolis:
- Howard W. Sams & Co., 1987. This text is loaded with useful commentary on
- Hayes Smartmodem programming, UART control, XModem file transfer, and CRC
- calculations. Its hallmark is a device-independent library of serial I/O
- functions. Campbell is also the author of The RS-232 Solution.
- Forsberg, Chuck. DSZ -- a ZMODEM-90TM File Transfer Program. Portland, OR:
- Omen Technology Inc., 1990. This is the user's manual for Forsberg's
- implementation of the Z-Modem protocol. This file is available in the file
- DSZ1190.ZIP on my BBS.
- Volkman, Victor. "TTYTALK: How Serial Telecommunication Works." The C Gazette
- (Summer 1988): pp 4-27. This article introduces TTYTALK, a C program CROSSTALK
- XVI compatible terminal program in C. Features include filtering, ASCII file
- transfer, command language, and dialing. Complete source code for this program
- is available in the file CGAZV3N1.ZIP on my BBS.
-
-
- Acknowledgements
-
-
- PCBoard is a registered trademark of Clark Development Corporation.
- ProcommPlus is registered trademark of Data-Storm Technologies, Inc. DESQview
- is a registered trademark of Quarterdeck Office Systems, Inc.
- Magna Carta Software, Inc.
- P.O. Box 475594
- Garland, TX 75047-5594
- Phone: (214) 226-6909
- BBS: (214) 226-8088 (1200/2400 baud)
- Figure 1 Source and Executable Sizes
- "TTYTALK Terminal" CCT Equivalent to
- Benchmark by VRV TTYTALK (CCT08.C)
- -------------------------------------------------------------
- Source size (.C) 23K 18K
- Executable size (.EXE) 17K 49K
- Figure 2 Effective File Transfer Rates at 2400 Baud
- ProcommPlus CCT Examples
- File Transfer by DataStorm #8 and #9
- ---------------------------------------------
- XModem-1K Send 226 cps 216 cps
- XModem-1K Receive 206 cps 205 cps
- Figure 3 Effective File Transfer Rates at 9600 Baud
- ProcommPlus CCT Examples
- File Transfer by DataStorm #8 and #9
- ---------------------------------------------
- XModem-1K Send 949 cps 905 cps
- XModem-1K Receive 857 cps 723 cps
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- The Complete C++ Primer
-
-
- Sam Hobbs
-
-
- Sam Hobbs is a nuclear engineer and project manager for GDS Associates. He has
- been involved with computers since 1966 and is an avid C fan. He assisted in
- the development of the Powerpro daily load forecasting and energy resource
- scheduling software package and the data conversion for the GrayBase nuclear
- power plant monthly operating report history database. Readers may contact Sam
- at GDS Associates, Inc., Suite 720, 1850 Parkway Place, Marietta, GA 30067,
- (404) 425-8100.
-
-
- This is an excellent book for several different audiences. First for newcomers
- to object-oriented programming, this book provides a gentle introduction with
- a welcome lack of hype. Second, the book is a thorough introduction to the C+
- + programming language. There is no implicit assumption that the reader is an
- advanced C programmer, but a sufficient knowledge to read simple, non-obscure
- C code is a practical requirement. A reader with little knowledge of C but a
- reasonable familiarity with some other procedural structured language can work
- through most of the book without any problems, but in one or two places there
- are short segments of code where not knowing C is a problem. The most
- prominent example is a brief section where the standard macros are used for a
- function with a variable number of arguments.
- The principal virtue of this book is its simplicity and clarity. The
- discussion of reference variables is especially clear. When there are
- lingering questions, the authors nearly always provide an answer within a page
- or two. Most of the time, the question is explicitly anticipated by the
- authors with a brief statement that the material will be covered within a few
- pages or in the next section of the book.
- Examples are chosen for their usefulness in explaining concepts in C++ and not
- because they are trendy or demonstrate how clever the authors are. As a
- result, most of the examples demonstrate how to use C++ in a very elementary
- way. The book is not a major repository of code which will be adopted by
- readers and used again and again; it is a place where readers will get the
- hard questions answered very clearly. Nevertheless, there are sections where
- the code demonstrates and suggests useful techniques that would not be
- immediately obvious to a beginning C++ programmer. Simple versions of iterator
- functions, using classes to define menus, and a memory buffer class
- demonstrate friends and inheritance. Function overloading and virtual
- functions are discussed with very simple-minded examples using several
- functions with one or two lines of code each and discussions of what overall
- program behavior results and why. Although these examples are simple almost to
- an extreme, the resulting clarity will serve the reader who really needs a
- primer far better than flashy and complex examples that are poorly understood.
- Other than a very brief mention in Chapter one, the C++ input/output stream
- operators are not discussed until Chapter 10. This contrasts to the approach
- in many other introductory C++ books where the stream operators are introduced
- early and used intensively from then on. Each approach has advantages, but for
- an introductory text on an evolutionary language there are substantial
- advantages to not introducing an entire new notation from the beginning. By
- deferring the discussion until near the end of the book, the myriad of
- questions on how and why the operators are used as they are can be treated
- naturally and without confusion.
- The book makes other departures from the customary organization of
- introductory books on C++. Objects and classes are introduced in Chapters two
- and three instead of near the middle or end of the book. The book then
- discusses new syntax and language features as they are introduced in light of
- the underlying rationale of C++ which is to add class and Object-Oriented
- extensions to the C language.
- Two things are wrong with the book. First, the book is overpriced at $45.00.
- The pricing is consistent with pricing for books that are an academic
- introduction to a programming language, and indeed this book can serve that
- purpose well and with no apologies, but its primary market is likely to be
- broader and more diversified. A lower and more competitive price would make it
- a must have. The second fault is that the book does not cover version 2 of
- C++, except in scattered brief mentions throughout the text and in a short
- appendix. This fault is less serious, given the introductory nature of the
- book. A beginning C++ programmer can learn more than enough to graduate to
- vendor manuals or to more sophisticated materials by studying this book. I
- highly recommend this one despite the price disadvantage.
- The Complete C++ Primer
- Keith Weiskamp and
- Bryan Flamig
- Academic Press, 1990
- $45.00, 524 pages
- ISBN 0-12-742687-6
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- The Art of Human-Computer Interface Design
-
-
- Tom Rombouts
-
-
- Tom Rombouts holds a bachelor's degree in Telecommunication and works in
- database software development for Ashton-Tate in Torrance, California.
- Although he loved the above book, he still doesn't like to use mice. He can be
- reached on USENET as tomr@ashtate. A.-T. com.
-
-
- The Art of Human-Computer Interface Design makes almost no mention of the C
- programming language. Yet it is also one of the most valuable books that a C
- programmer, as a software professional, could possibly read.
- Originally developed by the Human Interface Group at Apple Computer, this
- 540-page volume is a collection of more than 50 never-before-published pieces
- by a virtual Who's Who of leading user interface experts. Authorites from
- related non-computer fields are also included.
- The material spans the spectrum of user interface history, philosophy, theory,
- and technique. Anyone who reads this book will realize that user interface is
- a far deeper subject than deciding whether on-screen menu choices should use
- numbers or letters.
- The selections range from formal papers, to interviews, to light anecdotal
- ramblings. Despite the impressive credentials of the many authors, every page
- is readable and often entertaining. (A tribute to the non-jargon tone of the
- book is that the otherwise common abbreviation "UI" (user interface) is never
- used.)
- To reduce the anthology-like nature of the book, the manuscripts were shared
- and reviewed by all the contributors before publication. Thus,
- cross-references to other articles in the volume appear frequently.
- The book is organized into five sections of related contributions. Each
- section has a brief introduction. Introductory and closing remarks for the
- entire book also appear. Twenty pages of references, a contributors' gallery,
- and both a subject and a name index are included.
- It is impossible to do justice to such a mass of knowledge in this short
- space. I hope a brief description of each major section and a selected item or
- two from each will convey at least a rough sense of the book's overall tone.
- The first section, "Creativity and Design," introduces the broad principles,
- techniques, and problems of interface design. Some of the material is tutorial
- or historical, while other parts provide a behind-the-scenes look at software
- design.
- From this group, Scott Kim's "Interdisciplinary Cooperation" is a remarkable
- piece from which anyone in a technical field can learn. In it he illustrates
- how people from different disciplines (such as a graphic designer and a
- programmer) invariably have different backgrounds and values, and thus will
- almost always have at least some difficulty communicating with each other. He
- then outlines some specific steps to improve cooperation across such
- boundaries.
- The second section, "Users and Contexts," examines the range of users and
- situations involved in computer interaction. Educational, recreational, and
- productivity settings are all examined.
- Of these, "Koko's Mac II" stands alone. This is a fascinating look at how
- northern California's celebrated "talking" gorilla, Koko, actually learned to
- use a specially reinforced and programmed Mac II. (The picture on page 96 of
- the furry and massive Koko seated at the terminal will be instantly understood
- by anyone who has ever worked in end user support!)
- The next section, "Sermons," is a collection of candid, opinionated essays by
- some of the most notable names in the user interface field. Works appear by
- Alan Kay, Donald A. Norman, Ben Schneiderman, Jean-Louis Gassee, Timothy
- Leary, Ted Nelson, and Nicholas Negroponte.
- Not to be missed is Timothy Leary's piece, which opens by comparing the
- effects of LSD to future software interface designs as analogous methods of
- changing human consciousness. The reader can decide whether this essay
- presents a brilliant vision of the future or is merely a new form of
- psycho/techo-babble.
- The fourth section, "Technique and Technology," discusses specific advanced
- techniques such as animation, color, hyper-text, external input devices, voice
- interaction, and creating the illusion of reality.
- Two articles in this group, one on gestures in human communication and another
- on difficulties with audio computer interaction, together demonstrate how very
- different today's human-computer interaction is from normal human
- conversation.
- The fifth and final section, "New Directions," focuses on the future of user
- interface directions with terms such as "agents," "guides," and "cyberspace."
- This is perhaps the most fascinating material, because it touches on ideas
- that to date have only been seen in scien