home *** CD-ROM | disk | FTP | other *** search
Text File | 1996-02-07 | 2.6 MB | 77,685 lines |
Text Truncated. Only the first 1MB is shown below. Download the file for the complete contents.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Portable Byte Ordering in C++
-
-
- Philip J. Erdelsky
-
-
- Philip J. Erdelsky, Ph.D., is an R&D Engineer for Data/Ware Development, Inc.,
- in San Diego, California. He has been writing software in C and C++ for more
- than ten years. He can be reached at 75746.3411@compuserve.com.
-
-
-
-
- Which End is Up?
-
-
- I once facetiously asked a computer scientist where he stood on one of the
- greatest issues facing computing science. He must have sensed my mood, because
- he answered "squarely on the fence" before I could tell him what the issue
- was.
- The issue, of course, was byte order. Some CPUs, such as the Intel 80X86
- family, store multi-byte words in little-endian order, with the least
- significant byte (the little end) at the lowest address. Others, such as the
- Motorola 680X0 family, store them in big-endian order, with the most
- significant byte (the big end) at the lowest address. The terms "big endian"
- and "little endian" are supported by a literary allusion to Jonathan Swift's
- classic novel Gulliver's Travels, in which two nations fought a war to
- determine whether soft-boiled eggs should be opened at the big end or the
- little end.
- Each byte order has its own small advantage. Due to historical accident,
- numbers are written in big-endian order in English and other major western
- languages. That makes a memory dump easier to read if big-endian order is
- used. Addition, subtraction, and multiplication are done in little-endian
- order, so a little-endian CPU sometimes has a slight speed advantage when
- performing these operations in multiple precision.
- Could the world standardize on one byte order in the foreseeable future? It
- does not seem likely. The world cannot even decide which side of the road to
- drive on. The difference becomes a problem when little-endian and big-endian
- devices communicate with each other. It can be even more of a problem when
- their operating code has to be ported from one CPU to another with a different
- byte order.
- Writing a conversion routine is no problem. An experienced C programmer can
- whip one up in a minute. However, finding all the places where conversions are
- required can be difficult, unless the code was written with conversion in
- mind. That is where the techniques of C++ come in.
- First of all, a communication standard has to be established. If two devices
- communicate through a SCSI channel, all multi-byte values should be sent over
- the channel in big-endian order, which is the SCSI standard. Then conversions
- must be made to and from the CPU byte order, so the program can perform valid
- arithmetic operations on the data.
-
-
- The Types and Classes
-
-
- Listing 1 shows the header file endian.h, which contains nearly all the code.
- Listing 2 shows the file endian.cpp, which defines a useful union -- all the
- other code needed besides the header. The header defines three simple types,
- with names that are fairly standard:
- BYTE -- a single unsigned byte
- WORD -- a two-byte unsigned word
- DWORD -- a four-byte unsigned double word
- Variables of type WORD and DWORD are implicitly assumed to be in the order
- appropriate for the CPU, so the program can compute with them freely.
- The code also defines four classes of single and double words in specific byte
- orders:
- BEWORD -- a big endian WORD
- BEDWORD -- a big endian DWORD
- LEWORD -- a little endian WORD
- LEDWORD -- a little endian DWORD
- Of course, two of these types are substantially the same as WORD and DWORD,
- but the programmer does not need to know that while coding. The restrictions
- of C++ will prevent the program from performing arithmetic operations directly
- on them. This is important because such operations will become invalid when
- the program is ported to a CPU with a different byte order.
-
-
- Conversions
-
-
- Conversions from CPU order to big-endian or little-endian order are performed
- by a member function or by a class constructor. For example:
- LEWORD y(0xABCD);
- BEWORD x;
- x.set(0x1234);
- It is also possible to overload operator=, but this can cause problems in some
- implementations when unions containing these special types are initialized or
- assigned.
- Conversions from big endian or little endian order to CPU order are performed
- by a member function called value. For example, the following code adds 3 to a
- big endian WORD:
- BEWORD x;
- x.set(x.value() + 3);
- An attempt to do this in a nonportable fashion will be flagged as a
- compile-time error:
- BEWORD x;
- x = x + 3; // ERROR!
- In this case, Turbo C++ reports, "Operator cannot be applied to these operand
- type."
- The compiler knows the byte order of the CPU on which its object code will
- run, but will not reveal it at preprocessing time. If the programmer has this
- information, the code can be made more efficient by defining either_BIG_ENDIAN
- or _LITTLE_ENDIAN (but not both!) to indicate the byte order to be used. For
- example, if _BIG_ENDIAN is defined, then x.set(0x1234), when x is of type
- BEWORD, will generate the code for a simple assignment.
- If neither _BIG_ENDIAN nor _LITTLE_ENDIAN is defined, the compiler will
- generate less efficient code that will work on any CPU. For example, if x is
- of type BEWORD, x.set(0x1234) will generate code that performs the following
- operations:
- y = 0x1234
- first byte of x = y >> 8
-
- second byte of x = y
- If shifting is a particularly slow operation, it might be advisable to include
- a quick test for byte order at run time, and skip the shifting if the byte
- order of the CPU is the same as that of the word or double word being
- converted.
- If _RUN_TIME_ENDIAN is defined, the code will define a quick test, big_endian,
- which returns a true value (1) if it is executed on a big_endian CPU and a
- false value (0) otherwise. The code will also define a similar test called
- little_endian. These tests are used to skip the shifts where possible. In most
- implementations of C++, the tests involve no more code than testing a flag.
- Indeed, that is precisely how they are implemented. The initialized union
- _endian compiles with a 1 in either _endian.half[0] or _endian.half[1],
- depending on the byte order of the CPU.
- It is possible to overload operators, but it is generally more efficient to
- convert all multi-byte values to the CPU's byte order and use the regular
- operators. The only exceptions are operator== and operator!=, which do not
- depend on byte order as long as both operands use the same byte order, and
- tests against zero, which are implemented as the member functions zero and
- nonzero.
- All member functions have been defined inline, which makes them run fast and
- generates absolutely no code for member functions that are not called. If
- minimizing code size is desirable, it may be advisable to code some of them
- separately.
-
- Listing 1 The File ENDIAN.H
- // Portable Byte Ordering in C++
- // by Philip J. Erdelsky
- // Public Domain -- No Restrictions on Use
-
- // If the byte order of the target machine is known, include ONE of
- // the following statements:
- // #define _BIG_ENDIAN
- // #define _LITTLE_ENDIAN
-
- // If the byte order of the target machine is to be determined at run
- // time for each conversion, include the following statement:
- // #define _RUN_TIME_ENDIAN
-
- #ifndef _ENDIAN
- #define _ENDIAN 1
-
- typedef unsigned char BYTE;
- typedef unsigned short WORD; // two-byte word
- typedef unsigned long DWORD; // four-byte double word
-
- #ifdef _RUN_TIME_ENDIAN
-
- extern union_endian_union
- {
- DWORD whole;
- WORD half[2];
- } _endian;
-
- inline int big_endian(void) {return _endian.half[1];}
- inline int little_endian(void) {return _endian.half[0];}
-
- #endif
-
- // check for consistent parameter definitions
-
- #ifdef _BIG_ENDIAN
- #ifdef _LITTLE_ENDIAN
- #error _BIG_ENDIAN and _LITTLE_ENDIAN both defined
- #endif
- #ifdef _RUN_TIME_ENDIAN
- #error _BIG_ENDIAN and RUN_TIME_ENDIAN both defined
- #endif
- #endif
-
- #ifdef _LITTLE_ENDIAN
- #ifdef _RUN_TIME_ENDIAN
- #error _LITTLE_END1AN and _RUN_TIME_ENDIAN both defined
- #endif
- #endif
-
- class BEWORD // big endian WORD
- {
-
- union
- {
- BYTE half[2];
- WORD whole;
- } x;
- public:
- void set(WORD n)
- {
- #ifdef _BIG_ENDIAN
- x.whole = n;
- #else
- #ifdef_RUN_TIME_ENDIAN
- if (big_endian()) x.whole = n;
- else {
- #endif
- x.half[0] = n >> 8;
- x.half[1] = n;
- #ifdef _RUN_TIME_ENDIAN
- }
- #endif
- #endif
- }
- BEWORD(WORD n) {set(n);}
- WORD value(void)
- {
- return
- #ifdef _BIG_ENDIAN
- x.whole
- #else
- #ifdef RUN_TIME_ENDIAN
- big_endian() ? x.whole :
- #endif
- x.half[0] << 8 x.half[1]
- #endif
- ;
- }
- int zero(void) {return x.whole == 0;}
- int nonzero(void) {return x.whole != 0;}
- int operator == (BEWORD &n) {return x.whole == n.x.whole;}
- int operator != (BEWORD &n) {return x.whole != n.x.whole;}
- };
-
- class BEDWORD // big endian DWORD
- {
- union
- {
- BYTE quarter[4];
- DWORD whole;
- } x;
- public:
- void set(DWORD n)
- {
- #ifdef _BIG_ENDIAN
- x.whole = n;
- #else
- #ifdef _RUN_TIME_ENDIAN
- if (big_endian()) x.whole = n;
- else{
- #endif
-
- x.quarter[0] = n >> 24;
- x.quarter[1] = n >> 16;
- x.quarter[2] = n >> 8;
- x.quarter[3] = n;
- #ifdef _RUN_TIME_ENDIAN
- }
- #endif
- #endif
- }
- BEDWORD(DWORD n) {set(n);}
- DWORD value(void)
- {
- return
- #ifdef _BIG_ENDIAN
- x.whole
- #else
- #ifdef _RUN_TIME_ENDIAN
- big_endian() ? x.whole:
- #endif
- (DWORD) x.quarter[0] << 24 (DWORD) x.quarter[1] << 16
- x.quarter[2] << 8 x.quarter[3];
- #endif
- ;
- }
- int zero(void) {return x.whole == 0;}
- int nonzero(void) {return x.whole != 0;}
- int operator == (BEDWORD &n) {return x.whole == n.x.whole;}
- int operator != (BEDWORD &n) {return x.whole != n.x.whole;}
- };
-
- class LEWORD // little endian WORD
- {
- union
- {
- BYTE half[2];
- WORD whole;
- } x;
- public:
- void set(WORD n)
- {
- #ifdef _LITTLE_ENDIAN
- x.whole = n;
- #else
- #ifdef _RUN_TIME_ENDIAN
- if (little_endian()) x.whole = n;
- else {
- #endif
- x.half[1] = n >> 8;
- x.half[0] = n;
- #ifdef _RUN_TIME_ENDIAN
- }
- #endif
- #endif
- }
- LEWORD(WORD n) {set(n);}
- WORD value(void)
- {
- return
- #ifdef _LITTLE_ENDIAN
-
- x.whole
- #else
- #ifdef _RUN_TIME_ENDIAN
- little_endian() ? x.whole :
- #endif
- x.half[1] << 8 x.half[0]
- #endif
- ;
- }
- int zero(void) {return x.whole == 0;}
- int nonzero(void) {return x.whole != 0;}
- int operator == (LEWORD &n) {return x.whole == n.x.whole;}
- int operator != (LEWORD &n) {return x.whole != n.x.whole;}
- };
-
- class LEDWORD // little endian DWORD
- {
- union
- {
- BYTE quarter[4];
- DWORD whole;
- } x;
- public:
- void set(DWORD n)
- {
- #ifdef _LITTLE_ENDIAN
- x.whole = n;
- #else
- #ifdef _RUN_TIME_ENDIAN
- if (little_endian()) x.whole = n;
- else {
- #endif
- x.quarter[0] = n;
- x.quarter[1] = n >> 8;
- x.quarter[2] = n >> 16;
- x.quarter[3] = n >> 24;
- #ifdef _RUN_TIME_ENDIAN
- }
- #endif
- #endif
- }
- LEDWORD(DWORD n) {set(n);}
- DWORD value(void)
- {
- return
- #ifdef _LITTLE_ENDIAN
- x.whole
- #else
- #ifdef _RUN_TIME_ENDIAN
- little_endian() ? x.whole :
- #endif
- x.quarter[0] x.quarter[1] << 8
- (DWORD) x.quarter[2] << 16 (DWORD) x.quarter[3] << 24;
- #endif
- ;
- }
- int zero(void) {return x.whole == 0;}
- int nonzero(void) {return x.whole != 0;}
- int operator == (LEDWORD &n) {return x.whole == n.x.whole;}
-
- int operator != (LEDWORD &n) {return x.whole != n.x.whole;}
- };
-
- #endif
- /* End of File */
-
- Listing 2 The File ENDIAN.CPP
- #include "endian.h"
-
- #ifdef _RUN_TIME_ENDIAN
-
- union_endian_union_endian = {1};
-
- #endif
-
- // End of File
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Transferring Numeric Values Between Computers
-
-
- James A. Kuzdrall
-
-
- James A. Kuzdrall has been programming digital computers since 1960 and
- designing them since 1970. He is an MIT and Northeastern University graduate,
- and enjoys creating efficient algorithms and using computers extensively in
- engineering analysis and instrumentation control. He may be reached at Intrel
- Service Co., Box 1247, Nashua, NH 03061, (603) 883-4851.
-
-
-
-
- Overview
-
-
- Numeric transfers between dissimilar computers can be a vexing problem for
- engineers and scientists. Computers represent physical measurements by one of
- several binary codes that differ in length, byte order, and sometimes format.
- Direct binary data transfers between computers with different central
- processors is often impossible, forcing the use of ASCII decimal (text)
- equivalents.
- The six functions presented here offer a fast, compact, and format-independent
- alternative to ASCII transfers via printf and scanf conversions. Remarkably,
- these new system-independent C functions compile without any reference to the
- underlying details of the host's numeric system! You don't have to know which
- binary representation the host uses. The binary transfer format (two-byte
- integer, four-byte long integer, and four-byte floating point) requires 1/3.5
- to 1/5.5 times less file space or transmission time than an equivalently
- precise ASCII representation.
- The new functions are: fputi, fgeti, fputl, fgetl, fputf, and fgetf. Each
- takes two parameters, the address of the transfer data and a file (stream)
- pointer. All functions return a zero if successful, a nonzero integer if not.
- The functions accommodate transfers by modem, by network, by magnetic disk, or
- by tape. Any transfer facility accessible through putc and getc can be used.
- The transfer functions purposely avoid C library functions that may not be
- supported on older K&R compilers or on stripped-down microcontroller
- compilers. In fact, only putc and getc are needed.
-
-
- Applications
-
-
- The functions are particularly useful when transferring physical measurements
- from a microcontroller-based laboratory instrument to a more powerful computer
- for analysis. For example, a 68HC11-based optical radiometer might send its
- 1,600-measurement scan to an 80486-class computer for a report and to a VAX
- computer for inclusion in a scientific analysis. Radiometric measurements have
- accuracies of only two to four digits, but span ten decades from least measure
- to full scale., The floating-point functions fputf and fgetf allow the
- instrument to send normal units (watts, meters) rather than risk an eventual
- misinterpretation of scaled integers (microwatts-per-count,
- nanometers-per-count). In addition, the faster and more precise binary
- floating-point transfer requires less than one minute of 2400 baud modem time,
- whereas a full precision ASCII transfer requires over five minutes.
-
-
- Transfer Format
-
-
- Before looking at the algorithms, consider some consequences of the transfer
- format choice. Nonstandard formats, such as signed-magnitude integers and
- exponent/signed-mantissa floating-point formats, are tempting choices because
- they require less processing time and code in an instrument's
- performance-limited microcontroller. On the other hand, the more complex but
- widely used twos-complement integer and IEEE-754 floating-point formats allow
- programmers to create fast, simple, system-specific versions of the
- generalized transfer functions presented here in the many systems that are
- known to use these formats.
- Numeric range is another consideration. Simple formats often don't have the
- numeric range that users expect from the C data types. For example, the
- minimum signed-magnitude integer is --32,767, whereas the commonly used
- twos-complement form reaches --32,768. Choosing twos-complement and IEEE-754
- floating-point formats for transfer accommodates the expected range of C's
- common data types, albeit just the minimum range.
- Error reporting is another practical consideration. Since twos-complement
- numbers use the whole numeric range of the transfer integer, the transferred
- data itself cannot report an error. Instead, fputi and fputl indicate an
- out-of-range or a communication error by not sending the data and returning a
- nonzero integer. In the absence of some independent communication, the system
- receiving data detects an error when it gets less data than expected. Although
- the floating-point format has unused codes available for errors, fputf uses
- the same system for consistency. The sender distinguishes transfer errors from
- range errors by using ferror.
-
-
- Integer Transfers
-
-
- The integer transfer functions of Listing 1 take care of the byte order, the
- length, and the binary codings allowed in C. The C standard stipulates that
- integers use binary codes (disallowing binary-coded decimal) so that shift
- operators make sense. Although twos complement is by far the most common
- binary coding, the algorithm accepts signed magnitude, ones complement, and
- possibly others. Table 1 compares twos complement, signed magnitude (popular
- in analog-to-digital converters), and ones complement for important numbers in
- the representable range.
- The key to format-independent integer transfer is that all three binary
- formats represent positive numbers the same way. If integers are resolved into
- a positive magnitude (absolute value) and an independent sign flag, all
- formats will have the same binary representation. Once the integer is
- separated into sign and magnitude, C's format-independent bit-logic operations
- convert it to the twos-complement transfer format.
- Positive integers equal their magnitude, requiring only that the sign be
- noted. Negative integers are transformed to positive magnitudes using C's
- arithmetic negation -- most of the time anyway. The one exception is the most
- negative twos-complement number in Table 1. It has no positive counterpart.
- The value obtained for --(--32,768) is implementation-dependent and requires
- special handling.
- The code for fputi begins with macro definitions for MAXINT and MININT. MAXINT
- is always +32,767, an informal standard for K&R compilers and a requirement
- for Standard C compilers. MININT is one of two values:
- --32,767 if limited by the host compiler to the minimum C requirement
- --32,768 if limited by the range of the transfer integer
- MININT provides a way to test for twos-complement range without using -32,768
- in compilers where it is out of range.
- The transfer functions use unsigned integers (lsb and msb in fputi) where one
- might expect to see char variables. The longer length prevents overflow
- warnings in byte arithmetic and lost bits in left shifts. Unsigned integers
- also avoid the sign-fill uncertainty that occurs when using right shifts with
- signed numbers. A loophole in the C standard allows either the sign bit or
- zero to be put in the vacated upper bits [among other results -- pjp]. If your
- compiler puts in zeros, a small negative number suddenly becomes a large
- positive one! The unsigned type also prevents unwanted sign extension that
- would occur with a signed char. Finally, although many K&R compilers do not
- support unsigned char, all C compilers have unsigned integers.
- The first if statement traps --32,768 to prevent negation errors when
- obtaining the magnitude. If the number being sent is --32,768, the output
- bytes are set to the correct twos-complement value. For integer values other
- than --32,768, fputi splits the absolute value into two independent bytes.
- ANDing with 0xff limits the unsigned integers to eight bits. The magnitudes
- thus obtained are the same for any C integer format.
- The magnitudes of negative numbers are restored to their signed value in the
- transfer format by operations that are independent of the compiler's
- representation. If *ip is negative, the algorithm negates the bytes in twos
- complement by first performing a bit-wise complement then adding one.
- Comparing *ip with --1 avoids problems with --0. The exclusive-OR negates only
- the lowest byte, leaving the upper bytes as they are (zero), whereas C's
- bit-wise negate operator would affect the entire variable.
- The value at ip may have been out of range through all these operations if the
- host has integers longer than 16 bits. Postponing the range check until the
- end causes no processing problems because the value is masked down to bytes.
- The err variable eliminates an extra return. Unstacking variables for return
- consumes a significant amount of precious code space in a small
- microcontroller.
- The range check determines if the value held in the host's integer, possibly
- four bytes long, has fit into the two transfer bytes. The only safe
- conditional test for MININT is a test for equality, because a twos-complement
- compiler may (erroneously) choose to negate MININT in a test for greater than.
- Although no problems were encountered in testing with type int, two compilers
- failed with long. Ecosoft's Eco-C Compiler Version 3.10 (1983) thought MINLNG
- was greater than all other negative long values. IBM's C/C++ Version 2.0 for
- OS/2 (1992) decided all positive long values were less than MINLNG. The other
- compilers got it right.
- putc transfers the high-order byte (msb) first to create big-endian ordering
- in the file. The earlier byte masking serves another purpose when putc writes
- the bytes to the file. If lsb is not masked and happens to equal EOF, putc
- returns EOF whether or not there is an error.
- The second function of Listing 1, fgeti, converts the transfer format of fputi
- to the host's format. Since twos complement is so common, most hosts will
- accept the full range of the transfer format. For those that meet only the
- minimum C range, however, the reaction to --32,768 is unpredictable. If the
- binary is accepted, it becomes --0 and propagates the wrong data. If the
- processor traps --0 as an error, the program may stop. In both cases, an
- out-of-range error from fgeti seems preferable.
- After checking for transmission errors and masking, fgeti traps --32,768 if it
- is beyond MININT. The first if statement test creates a compile-time constant,
- TRUE or FALSE. The remaining portion always or never executes. Although the
- preprocessor's #if directive would serve better here, many K&R compilers do
- not support it. A code penalty is unlikely, however, because a compiler's
- optimizer often eliminates code sections that logically can't execute.
- Sign restoration relies on positive integers being the same in all integer
- formats. If the transferred number is negative, fgeti sets the neg flag then
- negates the twos-complement value, making it positive. It then assembles the
- positive value byte-wise in an integer, ans. The host's arithmetic negation of
- ans assures that the sign is properly installed. Again, --32,768 must be
- handled separately because it has no positive counterpart. Identification is
- easy, since it is the only negative integer that remains negative after
- negation (0x8000+1 = 0x7fff+1 = 0x8000).
- Knowing the host's format greatly simplifies both integer transfer functions.
- If the host is known to use 16-bit, twos-complement, MSB-first integers, use
- fwrite and fread for the transfer. If only the byte order is different
- (LSB-first), use memrev before fwrite and after fread.
-
-
- Long Integer Transfers
-
-
-
- The long integer transfer functions fputl and fgetl, in Listing 2, extend the
- integer transfer algorithm to four bytes. Like MININT, MINLNG equals either
- the host compiler's limit or the transfer limit, whichever is larger (least
- negative).
- At first glance, it might seem that the use of fputi and fgeti could simplify
- the coding. Just send the four-byte long integer as two two-byte integers.
- Unfortunately, the two least-significant bytes must be sent as an unsigned
- integer, and that doesn't work. Consider the long value 0x11118000. The lower
- bytes, 0x8000, are out-of-range for fgeti in some hosts. On the other hand, a
- host with 18-bit ones-complement integers would accept the value but produce
- the wrong bit pattern.
- As in the case of integers, the function code can be simplified dramatically
- if the host is known to use twos-complement integers.
-
-
- Floating-Point Transfers
-
-
- Listing 3 shows fputf and fgetf, functions which convert the host's float
- formats to and from the four-byte IEEE-754 transfer format. The IEEE float has
- one sign bit, an 8-bit base-2 exponent biased by 127, and a 24-bit normalized
- mantissa that ranges from 1.0 to just less than 2 (about 1.999999881). The
- mantissa's always-present leading one is removed to pack the float into four
- bytes. It is restored prior to arithmetic operations. Table 2 gives some
- examples of floating-point values in IEEE-754 format.
- If faced with manually converting decimal numbers to this format (perhaps as
- punishment for the sins of one's youth), you could multiply or divide a number
- by powers of two until it fell in the 1.0 to 2.0 range. A tally of these
- scaling factors gives the power-of-two exponent. The mantissa is then factored
- into its binary fraction bits -- with bits weighted 1.0, 0.5, 0.25, 0.125,
- etc. Binary fractions, however, reach the precision limit of a ten-digit
- calculator at 2 --12 , leaving the range 2 --13 to 2 --23 beyond
- representation.
- A better approach is to scale numbers to lie in the range 223 to 224--1. The
- binary factors are now easily represented as integers, 223 being 8,388,608.
- The IEEE-754 float value 0x3f800001 illustrates the practicality of the
- integer-factor approach and the difficulty of entering exact binary
- equivalents in decimal or of transferring them using printf. The binary
- factors are easily represented, but exact decimal representation of the total
- takes 24 digits.
- value= (223+1)/223 * 2127-127
- = (8388608+1)/8388608 * 1.0
- = 1.00000011920928955078125
- Interpreting the mantissa as an integer also proves useful in the float
- transfer algorithm. The algorithm scales the float to the range 224--1 to 223
- so that a float-to- long cast transfers all of the significant mantissa bits
- to the long. Another useful trick is exponent-only arithmetic, using only
- powers of two in scaling. Multiplication or division by powers of two means
- addition or subtraction in the exponent, which precludes errors caused by the
- finite mantissa precision. Even compilers limited to four-byte floats can
- perform the scaling and cast without error.
- Although the exponent of the popular IEEE-754 format represents powers of two,
- the exponent of the host format could represent powers of 4, 8, 10, or 16. As
- it turns out, these also convert without error when power-of-two scaling is
- used. Such formats do put some bits for the power-of-two factors in the
- mantissa, but the mantissa has more than enough precision to compute the
- modest 2127 range of the four-byte float without error.
- The precision of the compiler sets a practical limit on the powers of two used
- in scaling. The compiler's strtod must convert the ASCII decimal
- representation of the power-of-two constants without error. A compiler with 23
- bits of float precision can convert 20 to 224 without error, but fails on 225.
- The fputf code begins with a special check for 0.0, which does not respond to
- scaling. The next steps take care of the sign. Two cascaded while loops scale
- the number's absolute value to one. The first deals only with small numbers,
- multiplying them to values greater than one in relatively large steps of 28.
- This leaves the number greater than 1.0 but no more than 256.0. The test for
- expo greater than zero stops the loop if the host's float is too small for the
- IEEE-574 format.
- The second while loop handles numbers greater than one, including the result
- from the first while loop. This time all power-of-two factors present in the
- number are extracted. As in the first loop, initial scaling is in large 28
- increments until the number gets within range (less than 28 in this case). The
- expo test stops the loop if the transfer range is exceeded. This prevents
- lockup if *np was the IEEE-574 representation of infinity, which, like zero,
- does not scale.
- The two loops produce both the exponent and, upon multiplication by 223, the
- long integer mantissa. The encoding concludes with a byte-wise assembly of the
- IEEE-754 float in an array of characters. The range check uses the exponent
- rather than a maximum and minimum float value to avoid compiler inaccuracies
- in the ASCII-to-binary conversion. The putc loop sends the array (or zero) in
- MSB-first order.
- The choice of 28 as the maximum scale factor minimizes conversion time.
- Scaling by 2.0 could be used, of course, but it would require 127 divisions to
- reduce 2127 to 20. The largest allowed constant, 223, hits a maximum when *np
- is 2114, requiring four divisions by 223 plus 22 sub-power scalings for a
- total of 26 divisions. By contrast, 28 scales 2127 down to 20 in 22 divisions.
- As it turns out, the optimum maximum scale factor is the square root of the
- exponent. The choice of 28 optimizes conversions in the mid-range of exponents
- (2*1019 to 2*10-19 ) where most physical measurements fall.
- Moving on to fgetf, four bytes are checked for transmission errors as they are
- read into an array of unsigned integers, byt. Taking advantage of the loop set
- up for reading, they are also masked to eight bits and checked for zero. After
- unpacking the bytes to get expo and mant, a range check of expo intercepts
- erroneous or unintended values. Zero is also handled here since it won't
- scale.
- The multiply and divide loops build the float exponent factor by error-free
- power-of-two arithmetic. The loops scale quickly by whole 28 factors, then
- finish the remainder (27 to 20) in a single arithmetic operation. The variable
- pwr contains 2expo-127 when done. The separate treatment based on the initial
- expo value prevents underflow or overflow in four-byte float hosts.
- The error check detects most but not all host overflow problems. In hosts
- using four-byte float with an exponent bias of 128, for example, pwr usually
- denormalizes and/or reaches zero for numbers in the highest octave of the
- IEEE-754 range. The rest of the float range transfers accurately.
- If pwr seems valid, the code negates it if the original sign bit was set. An
- intermediate variable, ftemp, prevents underflow by controlling the order of
- arithmetic operations.
-
-
- Testing
-
-
- Two programs are included on this month's code disk that aren't listed here.
- The first is a program to generate a file of test numbers. The second is a
- program to read the test number file and test the transfer routines. The
- transfer functions send data that will be the same in all computers that
- receive it. Be aware when testing, however, that the float data sent may not
- exactly match the originator's data due to rounding or truncation. Instead,
- the originator must process its data through fputf and fgetf, perhaps using a
- file, to have the same values as the other systems.
- Assume two systems, A and B, must share data originating in A. For simplicity,
- assume they can exchange data on a common floppy-disk format. To test the
- functions, system A writes a range of data to a binary file using fputi,
- fputl, and fputf. System B reads the file using fgeti, fgetl, and fgetf. From
- its internal data, system B creates a second file using the fput functions. It
- should be the duplicate of the one B just read. System A reads both files with
- fget functions, comparing them item by item.
- Neither system should depend on a file-matching utility to compare the files,
- because A and B may use different fill characters in the unused space at the
- end of the file.
- Table 1 Integer Formats
- Decimal 2's Comp Sign-Mag 1's Comp
- -------------------------------------
- +32767 0x7fff 0x7fff 0x7fff
- +1 0x0001 0x0001 0x0001
- +0 0x0000 0x0000 0x0000
- -0 none 0x8000 0xffff
- -1 0xffff 0x8001 0xfffe
- -32767 0x8001 0xffff 0x8000
- -32768 0x8000 none none
- Table 2 IEEE-754 Floats
- Decimal IEEE-754 Sign Expo Mantissa
- -------------------------------------------------
- +largest 7f7fffff 0 fe ffffff (about
- 3.402822e+38)
- +32769.00 47000100 0 8e 800100
- +32768.00 47000000 0 8e 800000
- +32767.50 46ffff00 0 8d ffff00
- +32767.00 46fffe00 0 8d fffe00
- +1.000000 3f800000 0 7f 800000
- -1.000000 bf800000 1 7f 800000
- +smallest 00800000 0 01 800000 (about
- 1.17549e-38)
- +0.000000 00000000 0 00 800000 (zero by
-
- definition)
-
- Listing 1 Integer Transfer Functions
- /* Copyright (C) 1994 James A. Kuzdrall
- Free license to this software is granted only if the above
- copyright credit is included in the source code */
-
- /* ***< IHTRXF.CEE >***
-
- /* put system headers here */
- #include <stdio.h> /* usual definitions */
-
- /* use limit if does not exceed range of host machine */
- #define MAXINT 32767 /* limit +32767 */
- #define MININT -32768 /* limit -32768 */
- #define XFER_ERROR -1 /* a non-zero integer */
-
- /***< fputi >*** ++++++++++++++++++++++++++++++++++++++++++++++ *
-
- USE ...... Convert host's integer to the 2-byte, 2's complement,
- MSB-first standard transfer format. Send the two bytes to the
- output stream fp.
- RETURNS... Non-zero if error; otherwise, 0
- No number sent if error.
- ERRORS.... Disk errors of putc().
- Overrange for integers beyond transfer range, 32767 to -32768,
- */
-
- int fputi(ip,fp)
- int *ip; /* integer to put on stream */
- FILE *fp; /* stream (file) pointer */
- {
- int absi; /* absolute value to transfer */
- unsigned int msb; /* build 2's complement here */
- unsigned int lsb;
- int err;
-
- /* detect special case, -32768 */
- if( MININT != -32767 && *ip == MININT ) {
- lsb= 0: /* set 2's complement -32768 */
- msb= 0x80;
- }
- else {
- /* avoid problem with -MININT in 2's comp */
- absi= (*ip <= -1 && *ip >= -32767) ? -*ip: *ip;
-
- /* divide magnitude into bytes */
- lsb= absi & 0xff; /* mask to one byte */
- msb= (absi >> 8) & 0xff;
-
- /* negate bytes in 2's comp if number is negative */
- if( *ip <= -1 ) { /* less than -0 */
- lsb= (lsb ^ 0xff) +1;
- msb= (msb ^ 0xff) + (lsb >> 8); /* add carry, if any */
- }
- }
-
- err= XFER_ERROR; /* preset to error */
- /* if in range, send MSB first */
-
- if( *ip <= MAXINT && (*ip >= -32767 *ip == MIHINT) &&
- putc(msb,fp) != EOF && putc(lsb,fp) != EOF )
- err= 0;
-
- return( err ):
- }
-
- /***< fgeti >*** ++++++++++++++++++++++++++++++++++++++++++++++ *
-
- USE ....... Read a standard format, 2-byte, 2's complement, MSB-
- first integer from the stream fp and convert it to the host's
- integer format.
- RETURNS... Non-zero if error; otherwise, 0
- Places answer at pointer if no errors.
- ERRORS.... From getc() only; generates no errors of its own.
- */
-
- int fgeti(ip,fp)
- int *ip; /* place where answer goes */
- FILE *fp; /* file pointer given by fopen() */
- {
- unsigned int msb,lsb; /* might be EOF */
- int ans;
- char neg; /* non-zero (true) if original nr was negative */
- int err;
-
- err= XFER_ERROR; /* preset */
- /* transmission errors */
- if( (msb= getc(fp)) == EOF (lsb= getc(fp)) == EOF )
- goto gixit; /* data error */
-
- msb &= 0xff; /* remove unwanted high bits, if any */
- lsb &= 0xff;
-
- /* detect special case, -32768 in non-2's complement host */
- if( MININT == -32767 && msb == 0x80 && !lsb )
- goto gixit; /* out-of-range error */
- err= 0; /* no other errors possible */
-
- /* use 2's comp negate to make negative nors positive; save sign */
- if( (neg= (msb & 0x80)) ) {
- lsb= ((lsb & 0xff) ^ 0xff) +1; /* perhaps produces carry bit */
- msb =(((msb & 0xff) ^ 0xff) + (lsb >> 8)) & 0xff;
- }
-
- /* cascade bytes to form positive int, then correct the sign */
- if( msb & 0x80 ) /* only MININT is still neg */
- *ip= MININT;
- else { /* for all but MININT */
- ans= (msb << 8) + (lsb & 0xff);
- *ip = neg ? -ans : ans; /* let host install sign */
- }
-
- gixit:
- return( err );
- }
-
- /* End of File */
-
-
-
- Listing 2 Long Transfer Functions
- /* Copyright (C) 1994 James A. Kuzdrall
- Free license to this software is granted only if the above
- copyright credit is included in the source code */
-
- /* ***< LNGTXF.CEE >***
-
- /* put system headers here */
- #include <stdio.h> /* usual definitions */
-
- /* use limit if does not exceed range of host machine */
- #define MAXLNG 2147483647L /* limit +2147483647 */
- #define MINLNG -2147483648L /* limit -2147483648 */
- #define XFER_ERROR -1 /* a non-zero integer */
-
- /***< fputl >*** ++++++++++++++++++++++++++++++++++++++++++++++ *
-
- USE....... Convert host's long integer to the 4-byte, 2's comple-
- ment, MSB-first standard transfer format. Send the four bytes
- to the output stream fp.
- RETURNS... Non-zero if error; otherwise, 0
- No number sent if error.
- ERRORS.... Disk errors of putc().
- Overrange if exceeds transfer range, 2147483647 to -2147483648.
- */
-
- int fputl(lp,fp)
- long *lp; /* long to put on disk */
- FILE *fp; /* file pointer given by fopen() */
- {
- int ix; /* byte counter */
- long absl: /* absolute value of *lp */
- unsigned int byt[5]; /* byt[0] is most significant */
- int err;
-
- if( MINLNG != -2147483647 && *lp == MINLNG ) {
- byt[0]= 0x80; /* set 2's complement -2147483648 */
- byt[1]=byt[2]=byt[3]=0
- }
- else {
- /* avoid problem with -MINLNG in 2's comp, 4-byte */
- absl= (*lp <= -1L && *lp >= -2147483647) ? -*lp : *lp;
-
- /* divide magnitude into bytes; if neg, negate in 2's comp */
- byt[4]= 0x100; /* becomes 1 after >> 8 */
- for( ix= 3; ix >= 0; ix-- ) { /* process from LSB to MSB */
- byt[ix]= (absl >> (8*(3-ix))) & 0xff;
- if( *lp <= -1L )
- byt[ix]= (byt[ix] ^ 0xff) + (byt[ix+1] >> 8);
- }
- }
- err= XFER_ERROR; /* preset */
- /* if in range, send MSB first */
- if( *lp <= MAXLNG && (*lp >= -2147483647 *lp == MINLNG) ) {
- for( ix= 0; ix < 4; ix++ )
- if( putc(byt[ix],fp) == EOF )
- goto plxit;
- err=0;
-
- }
- plxit:
- return( err );
- }
-
- /***< fgetl >*** +++++++++++++++++++++++++++++++++++++++++++++++*
-
- USE....... Read a standard format, 4-byte long integer from the
- input stream fp and convert it to the host's long format.
- RETURNS... ERROR if error; otherwise, 0
- Places answer at pointer if no errors.
- ERRORS.... From getc() only; generates no errors of its own.
- */
-
- int fgetl(lp,fp)
- long *lp; /* place where answer goes */
- FILE *fp; /* file pointer given by fopen() */
- {
- int ix; /* byte counter */
- char neg; /* non-zero (true) if original nr was negative */
- long ans; /* temporary */
- unsigned int byt[5]; /* byt[0] is most significant */
- int err;
-
- err: XFER_ERROR; /* preset */
- for( ix= 0; ix<4; ix++ ) { /* byte read from MSB to LSB */
- if( (byt[ix]= getc(fp)) == EOF )
- goto glxit;
- byt[ix] &= 0xff; /* remove any high bits */
- }
-
- /* detect special case, -2147483648 in non-2's complement host */
- if( MINLNG == -2147483647 && byt[0] == 0x80 && !byt[1] &&
- !byt[2] && !byt[3] )
- goto glxit; /* out-of-range error */
- err= 0; /* no error */
-
- ans= 0L;
- byt[4]= 0x100; /* becomes 1 after >> 8 */
- neg= byt[0] & 0x80;
- for( ix= 3; ix >= 0; ix-- ) {
- if( neg ) /* make negative nrs positive by 2's comp negate */
- byt[ix]= (byt[ix] ^ 0xff) + (byt[ix+1] >> 8);
- ans = ((long)(byt[ix] & 0xff)) << 8*(3-ix);
- }
-
- /* handle MINLNG separately; has negate and range problems */
- if( byt[0] & 0x80 ) /* only MINLNG is still neg */
- *lp= MINLNG;
- else /* for all but MINLNG /*
- *lp= neg ? -ans : ans; /* let host install sign */
-
- glxit:
- return( err );
- }
- /* End of File */
-
-
- Listing 3 Floating-Point Transfer Functions
-
- /* Copyright (C) 1994 James A. Kuzdrall
- Free license to this software is granted only if the above
- copyright credit is included in the source code */
-
- /* ***< FLTTXF.CEE >***
-
- #include <stdio.h> /* ANSI C definitions */
- #define XFER_ERROR -1 /* a non-zero integer */
-
- static float pwr2[9]= {
- 1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0, 256.0 };
-
- /***< fputf >*** ++++++++++++++++++++++++++++++++++++++++++++++*
-
- USE....... Convert host's float to a 4-byte, IEEE-754, MSB-first
- standard transfer format. Send the 4 bytes to output stream fp.
- RETURNS... Non-zero if error; otherwise, 0
- No number sent if error.
- ERRORS.... Disk errors of putc().
- Out-of-range for absolute values beyond IEEE 4-byte float range,
- about 3.402822e+38 to 1.175493e-38.
- */
-
- int fputf(np,fp)
- float *np; /* number to be sent */
- FILE *fp; /* stream (file) pointer */
- {
- float nr; /* local copy of number */
- char iszero; /* flag: 1 if nr is 0.0, else 0 */
- int expo; /* build exponent here */
- int ix; /* index for powers of two */
- long mant; /* mantissa */
- unsigned int byt[4]; /* build IEEE float here */
- int err;
-
- byt[0]= iszero= 0; /* preset float sign + and not zero */
- expo= 127; /* preset exponent bias at 2^0 */
- if( (nr= *np) == 0.0 ) { /* zero won't scale; just send it */
- iszero= 1;
- goto pfout; /* note: preset expo passes pfout test */
- }
-
- /* get magnitude; put sign bit output byte */
- if( nr < 0.0 ) {
- byt[0]= 0x80;
- nr= -nr;
- }
-
- /* scale nr to 2^0 to 2^1 range; build exponent by powers of 2 */
- while( nr < 1.0 && expo > 0 ) { /* make small numbers > 1.0 */
- nr *= 256.0;
- expo -= 8;
- }
- for( ix= 8; --ix; ) {
- while( nr >= pwr2[ix] && expo < 255 ) {
- nr /= pwr2[ix);
- expo += ix;
- }
- }
-
-
- /* scale nr to 2^24 to 2^23 range; convert to long integer */
- mant= (long )( nr * 8388608.0 ); /* 2^23 */
- /* get mantissa bytes from the long */
- byt[3]= mant;
- byt[2]= (mant >> 8);
- byt[1]= (mant >> 16);
-
- /* encode exponent */
- if( expo & 0x0001 ) /* put lsb of expo in implied bit */
- byt[1] = 0x80;
- else
- byt[1] &= 0x7f;
- /* combine rest of exponent and sign */
- byt[0] = (unsigned int )expo >> 1;
-
- pfout:
- err= XFER_ERROR; /* preset */
- /* look for out-of-range exponent and write errors */
- if( expo < 255 && expo > 0 ) {
- for( ix= 0; ix < 4; ix++ )
- if( putc( (iszero ? 0 : (byt[ix] & 0xff)),fp) == EOF )
- goto pfxit;
- err= 0;
- }
- pfxit:
- return( err );
- }
-
- /***< fgetf >*** +++++++++++++++++++++++++++++++++++++++++ *
-
- USE....... Read a standard transfer format, 4-byte, IEEE-754, MSB-
- first float from the stream fp and convert it to the host's
- float format.
- RETURNS... Non-zero if error; otherwise, 0
- Places answer at pointer if no errors.
- ERRORS.... From getc().
- Bad exponent byte (255).
- */
-
- int fgetf(np,fp)
- float *np; /* place where answer goes */
- FILE *fp; /* stream (file) pointer*/
- {
- int ix; /* byte counter */
- char nonzero; /* true if any bits set */
- int expo; /* exponent of IEEE float */
- long mant; /* fractional part */
- float pwr; /* scale factor from exponent */
- float ftemp;
- unsigned int byt[4]; /* transferred bytes, byt[0] is MSB */
- int err;
-
- /* read bytes from disk; check error */
- err= XFER_ERROR; /* preset true */
- nonzero= 0; /* preset false */
- for( ix= 0; ix<4; ix++ ) { /* byte read from MSB to LSB */
- if( (byt[ix]= getc(fp)) == EOF )
- goto gfxit; /* disk error, quit */
-
- byt[ix] &= 0xff; /* strip parity etc, if present */
- if( byt[ix] ) /* see if any bits set */
- nonzero++;
- }
-
- /* divide IEEE-754 float into parts */
- expo= ((byt[0] << 1) (byt[1] >> 7)) & 0xff;
- mant= ( ( (long )(byt[1] 0x88) << 16) /* implied bit */
- + (byt[2] << 8) + byt[3] );
-
- /* intercept IEEE-754 Inf, Nan, denormalized or just bad byte */
- if( expo == 0xff (!expo && nonzero) )
- goto gfxit;
-
- /* special case: number is zero */
- if( !nonzero ) {
- *np= 0.0;
- err= 0;
- goto gfxit;
- }
-
- /* build float value of exponent in pwr */
- pwr= 1.0; /* 2^0 scaling multiplier corresponds to expo=127 */
- if( expo > 127 ) {
- while( expo > 135 ) {
- pwr *= 256.0;
- expo -= 8;
- }
- pwr *= pwr2[expo-127];
- }
- else {
- while( expo < 119 ) {
- pwr *= .00390625; /* equiv /= 256.0 */
- expo += 8;
- }
- pwr /= pwr2[127-expo];
- }
-
- /* check for over and under flow */
- if( pwr > 0.0 ) {
- if( byt[0] & 0x80 )
- pwr= -pwr; /* set right sign */
- /* convert mantissa to 1.0 to 2.0 range then scale by pwr */
- ftemp= (float )mant/8388608.0; /* mant/2^23 */
- *np= ftemp * pwr; /* separate lines prevent mant*pwr overflow */
- err= 0;
- }
- gfxit:
- return( err );
- }
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Linux - The Low Cost UNIX
-
-
- Rick Roberts
-
-
- Rick Roberts is president of Wintech Solutions, a technical software
- consulting company in Arlington, Texas, specializing in user friendly custom
- technical software and embedded system design. He can be reached via email at
- ricrob@metronet.com or by phone at (817) 860-7591.
-
-
-
-
- What is Linux?
-
-
- UNIX for free? Yes, if you have access to the Internet, you can obtain a fully
- functional, 32-bit, UNIX operating system for nothing. If you don't have
- access to the Internet, there are several other ways of acquiring it, with the
- cost depending on the method you choose, as I explain later.
- This UNIX clone, called Linux, is designed specifically for 386/486 ISA-bus
- machines. Linux includes the following features: X-Window (based on the
- Xfree86 X server), the GNU C compiler, the GNU shell Bash, man pages, support
- for up to four virtual terminals, network and mail support, and all of the
- standard UNIX utilities. Hardware supported includes AT-type MFM, RLL, ESDI,
- and IDE hard disks, high density 3.5 and 5.25 inch floppy drives, SCSI, serial
- and bus mice, and several VGA cards at up to 1028x768x256 resolution. A
- typical Linux distribution will also include some application programs as
- well, such as spreadsheets, TeX, various games, communication programs, and
- others.
- Linux is POSIX-compliant and implements a subset of System V, so it's usually
- very easy to port applications written for other UNIX systems to run under
- Linux. For instance, I have compiled and run some of the X-Window code
- examples from the O'Reilly books without any modifications. You may even be
- able to use some commercial applications if they are distributed with source
- code so that you can compile them under Linux.
- Linux (the 'i' is pronounced as the 'i' in UNIX) was created by Linus
- Torvalds. It began as a simple task switcher running two processes, which
- printed "AAAA" and "BBBB." Gradually, the task switcher grew until it evolved
- into a full UNIX-compatible kernel. Over the years, many people on the
- Internet have contributed code to make it what it is today. Linux is not
- public domain; it is copyrighted by Linus Torvalds. Fortunately, Torvalds'
- copyright is the same as the GNU General Public Licence, a relatively liberal
- set of restrictions that actually promote the free distribution of software
- (see the sidebar, "The Linux Copyright"). Since Linux was totally written from
- scratch, all of its source code can be distributed without royalties.
-
-
- System Requirements
-
-
- You will need at least 4 Mb of memory to run Linux. After that, your memory
- requirements will vary, depending upon what applications you use and upon your
- patience: while you may get by on 4 MB (Linux implements virtual memory), some
- tasks will cause your machine to run noticeably slower, as Linux constantly
- swaps to disk when you run out of memory. Two such applications are the C
- compiler and X-Window. Your hard disk should have a minimum of 40 MB free, 65
- MB if you want X-Window. You will probably want to set aside at least an extra
- 10 MB for user files.
-
-
- Getting Linux from the Internet
-
-
- Linux is available via anonymous ftp at several Internet sites. Some of these
- are shown in Table 1. Not all sites carry exactly the same distribution. As
- Linux is a product of the Internet, it has grown through the contributions of
- many different people; so you will find several different flavors of it on the
- net. Usually, the difference in flavor will be in the kernEl's revision
- number; you can expect different flavors to have different functionality.
- Other variances occur in the hardware supported (for example, one version
- supports Sound Blaster cards), enhanced features and code improvements, which
- bugs got fixed, application programs included, and the installation procedure.
- A typical Linux distribution includes the binaries (compiled executables), so
- all you need to do is follow the installation procedure and the directory
- structure will be setup for you automatically. You will be ready to run Linux
- without having to compile it. Two of my favorite Linux distributions are
- SlackWare's and Texas A&M University's (TAMU). Both have an easy installation
- procedure and come with lots of auxiliary programs. The following session log
- shows a user connecting to the TAMU site and getting one of the disk images:
- bash$ ftp net.tamu.edu
- Connected to net.tamu.edu.
- 220 saturn FTP server (Version wu-2.1c(5) Fri Jan 14
- 16:46:43 CST 1994) ready.
- Name (net.tamu.edu:ricrob): anonymous
- 331 Guest login ok, send your complete e-mail address as
- password.
- Password:
- Remote system type is UNIX.
- Using binary mode to transfer files.
- ftp> cd /pub/linux/TAMU.99p12/image.3inch
-
- 250 CWD command successful.
- ftp> binary
- 200 Type set to I.
- ftp> get bin.01
- 200 PORT command successful.
- 150 Opening BINARY mode data connection for bin.01
- (1474560 bytes).
-
- 226 Transfer complete.
- 1474560 bytes received in 80.33 seconds (17.93 Kbytes/s)
- ftp> quit
- 221 Goodbye.
-
-
-
- Installing Linux
-
-
- Once you have downloaded all the disk images, you must place them on
- individual floppy disks. Some sites use MS-DOS compatible files while others
- use Linux-format files. Even if the site uses DOS-format disks, you will still
- need a Linux formatted installation boot disk (this is usually the disk made
- from the file a1). You can prepare this disk using a raw disk writing utility,
- such as rawrite.exe; or if you already have Linux running on another machine,
- you can use that machine to make up the disks using the UNIX dd command. You
- can get rawrite.exe from wcarchive.cdrom.com in the pub/linux/old_slackware
- directory.
- After making up the set of installation floppies, boot your computer using the
- installation boot disk. During the boot process, the Linux kernel will be
- loaded into memory. If all goes well during the boot, you should see a login
- prompt. At this point Linux is running and you will need to be familiar with
- UNIX commands to proceed. Login as root and search the directory for read.me
- files that will give you information on the installation process. Also, be
- sure to read any read.me files and the FAQ (frequently asked questions) file
- from the distribution site.
- The first thing you must do before going any further is create a partition on
- your hard disk for Linux. Creating a Linux partition will destroy all data on
- your hard disk, so you may as well start with an empty hard disk. To create
- the partition, use the fdisk program from the boot disk. After fdisk, you will
- need to reboot. At this point, it is a good idea to have an understanding of
- UNIX system administration. UNIX is a much more complicated and unforgiving
- operating system than MSDOS. UNIX uses disk caches which must be flushed prior
- to rebooting. Before you reboot, run the sync command a couple of times to
- write the cache to disk. Also, if halt is on the boot floppy, run that after
- the sync command. halt prepares the operating system for a power off.
- After rebooting, run the installation shell script. It probably will be named
- something like setup. Simply follow all the prompts and that's all there is to
- it.
- One nice thing about the TAMU distribution is that it will install LILO (LInux
- LOader), the disk boot loader program. This program enables you to boot Linux
- from a hard disk instead of a floppy. As your computer boots up, LILO will ask
- you which partition you wish to boot from, which allows you to boot either DOS
- or Linux. If you don't select anything LILO times out and boots from your
- first partition. TAMU also includes a menu system that makes it easy to do
- things like format disks and setup new users.
- Although the TAMU release has a very straightforward installation procedure, I
- found some peculiarities as well. The TAMU release is supposed to work with 4
- MB memory, but I could not get the installation to complete successfully with
- only 4 MB. The installation would fail and display an "out of memory" message
- during the mkefs part of the installation. When I added more memory (16 more
- MB), I was able to get through the file system creation but bombed out while
- loading from the second installation disk. This second blowup, I learned, was
- probably due to a peculiarity with my computer. Evidently, I had installed too
- much memory. I dropped down to 8 MB and had no more problems. Once Linux was
- installed, it was able to run with 4 MB of memory, but as it was booting up,
- an out-of-memory message was displayed. After that I ran the system with 4 MB
- for a while, and didn't seem to have any difficulties, even while running
- X-Window. However, the system was constantly swapping out memory to the hard
- disk and ran very slow, so I would be wary of running the system with only 4
- MB.
-
-
- Using Linux
-
-
- Once you have successfully installed Linux and are up and running, find and
- read all of the read.me files and FAQs. You should add a new user account as
- soon as possible and login with it, so you don't have to login as root with
- superuser privileges. Since you will be the system administrator, I suggest
- you get a good book on system administration such as the UNIX System
- Administration Handbook by Nemeth, Snyder, and Seebass. [1]
- Linux provides a wonderful opportunity for those wanting to learn about UNIX
- or operating systems in general. Because the source code is available you can
- learn all the ins and outs of operating system design -- you can even add your
- own embellishments. If you come up with something really useful, you can
- submit it as a contribution for the next release. You can also learn a lot
- about UNIX system administration. Linux will allow your 386/486 to do true
- multitasking with multiuser capabilities, and since it is POSIX compatible,
- you will be able to run lots of the free UNIX software available on the
- Internet.
- If you can connect to the Internet, a newsgroup named comp.os.linux devoted to
- Linux can provide current information about it. Another useful newsgroup for
- general UNIX questions -- such as how to create a directory, etc. -- is
- comp.unix.questions.
-
-
- Other Linux Sources
-
-
- If you cannot access the Internet, you can obtain Linux from several
- commercial sources. Some companies sell Linux already installed and runnable
- on a CDROM. Table 2 lists several commercial sources of Linux. Also, several
- computer magazines have advertisements for companies selling Linux. As
- mentioned before, because Linux is a product of the Internet and is in a
- constant state of flux, different sources will carry different versions and
- kernel patch levels. So before spending any money be sure to find out if their
- version is compatible with your hardware.
-
-
- Bibliography
-
-
- [1] Nemeth, Snyder, and Seebas. Unix System Administration Handbook. Prentice
- Hall, ISBN 0-13-933441-6.
- The Linux Copyright
- How is Linux copyrighted and how does this concern you? That depends on how
- you intend to use Linux. If, like most people, you will just be an end user,
- you probably need to read no further. What if you want to use Linux as a
- software development platform? That's still probably not a problem, unless you
- develop software that specifically uses Linux and is intended for outside
- distribution. Here things start to get a little murky. There's a difference
- between just making system calls (which is okay), and incorporating Linux code
- as part of your own. If you intend to do the latter, you'll have to meet a few
- requirements, as described in the rest of this sidebar. The same is true if
- you intend to redistribute Linux, or any modified portion thereof.
- The Linux copyright applies the same restrictions as another, well-known
- copyright, the GNU General Public License (GPL), a.k.a. the GNU CopyLeft. The
- GPL is a software license offered by the Free Software Foundation, designed to
- promote rather than discourage the free distribution and use of software. The
- GPL achieves this by placing certain requirements upon persons who distribute
- the licensed software.
- At the heart of the GPL are three requirements: 1) to make source code
- available along with any GPL-licensed software that is distributed; 2) to
- include a disclaimer of warranty; 3) to propagate the GPL license with the
- distribution.
- The first requirement basically states that anyone who distributes software
- covered by the GPL must be prepared to supply the accompanying source code to
- users who request it. The same requirement applies to anyone who modifies and
- distributes GPL-covered software, or incorporates portions of it in code that
- they distribute. This requirement in effect prevents distributors from
- reselling the code as proprietary software. (Distributors may, however, charge
- a fee for distributing the software, and for distributing the source code.)
- The second requirement says that persons who distribute GPL-licensed software
- must make clear that the software's original author provides no warranty
- protection. This requirement protects software authors who are willing to make
- their software widely available but cannot afford to provide extensive
- support.
- Finally, the third requirement says that distributors must place on each copy
- a conspicuous GPL copyright notice, and include an unedited copy of the GPL
- text. Thus, if the software is distributed to yet another distributor, the GPL
- will propagate itself through any succeeding distributions.
- The above description outlines only the major points of the GPL; there are
- numerous details too lengthy to reprint here. Also, the GPL is subject to
- change without notice. For these reasons you may want obtain a copy of the GPL
- text. You can get the GPL text by writing to Free Software Foundation, 675
- Mass Avenue Cambridge, MA 02139.
- Marc Briand
- Managing Editor
- Table 1 Linux FTP sites
- net.tamu.edu
- wcarchive.cdrom.com
- sunsite.unc.edu
- wustl.edu
- tsx-11.mit.edu
- uu.net
- nic.funet.fi
- Table 2 Commercial Sources of Linux
- Softlanding Software (SLS) 910 Lodge Avenue, Victoria British
- Columbia,
- Canada; (604)-360-0188. They have several
- prepackaged distributions which vary from
- a bare
- system to a full system with X-Window,
- TeX,
- TCP/IP support.
-
- Linux Systems Labs 49884 Miller Ct., Chesterfield, MI
- 48047-2333; (810)
-
- 716-1700.
-
- Walnut Creek (CD-ROM) 4041 Pike Lane, Suite E, Concord, CA 94520;
- (800)-786-9907.
-
- America Online (a commercial (800)-827-6364.
- on-line service)
-
- Just Computers! P.O. Box 751414, Petaluma, CA 94975-1414;
- (707)-769-1648; linux@justcomp.com.
-
- InfoMagic Incorporated P.O. Box 30370, Flagstaff, AZ 86003-0370;
- (CD-ROM) (602)-526-9565; info@infomagic.com.
-
- JANA Publishing (CD-ROM) jana@canrem.com.
-
- Morse Telecommunications linux@morse.net.
- (CD-ROM)
-
- Nascent Technology P.O. Box 60669, Sunnyvale, CA 94088-0669;
- (CD-ROM) nascent@netcom.com
-
- Spheric Microsystems 1900 Empire Blvd. #120, Webster, NY 14580;
- Incorporated (CD-ROM) (800)-869-8649 or (716)-586-4900;
- info@spheric.com.
-
- Trans-Ameritech (CD-ROM) roman@trans-ameritech. com
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- An Introduction to Floating-Point C Extensions
-
-
- Jim Thomas and Jerome T. Coonen
-
-
- This article is not available in electronic form.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Advanced C++
-
-
- Nimish R. Doshi
-
-
- Nimish Doshi is an experienced software engineer currently developing
- graphical user interfaces in New Jersey. He has an M.S. in computer science
- and his computer interests include object-oriented technology and distributed
- applications. He can be reached via e-mail at ej798@cleveland.Freenet.Edu.
-
-
-
-
- Introduction
-
-
- When I first picked up Advanced C++, by Namir Clement Shammas, I was an
- intermediate C++ developer whose first book on the subject was A1 Steven's
- Teach Yourself C++. This first book served as a nice tutorial, but left me
- hungry for more information on how to take advantage of C++'s many rich
- capabilities. That is why I turned to Advanced C++. Snammas's title is
- somewhat of a misnomer, since his book is as much about object-oriented
- programming and data structures as it is about advanced C++. The book is
- divided into three parts, with two-thirds of the material dedicated to data
- structures modeled as C++ classes. This pleasantly provided me much more
- information than I was prepared to grasp!
- The title implies that the book is intended for readers with at least a
- working knowledge of C++. Shammas does not assume his readers are experts
- however. The book is also accessible (an important feature for this kind of
- book, whether you're an expert or not) thanks to Shammas's clear writing style
- and abundant code examples. The examples are annotated with discussions that
- account for alternative ways of implementation. All examples are also included
- on an MS-DOS formatted disk, which accompanies the book. These examples will
- compile with an IBM-PC-compatible C++ compiler (e.g. Borland). The book's
- length (784 pages) may seem intimidating to some, but needlessly so. The extra
- length merely serves the book's purpose to make the C++ programmer proficient.
- I must say that the book fulfills its purpose very well.
-
-
- Advanced Components of C++
-
-
- Shammas opens up the first twelve chapters with advanced issues of C++. The
- chapters serve as an advanced tutorial rather than a plethora of tricks of the
- language.
- Shammas first reviews reference parameters, default arguments, overloading,
- and template functions. He encourages the use of default parameters to avoid
- excessive overloading of functions, with their multiplicity of signatures. The
- template function is the key to eliminating repetitious code, as it allows the
- same function to handle a variety of data types. Most template functions
- perform some kind of relational comparison. Shammas reminds us to always
- provide relational operators for those classes which are used as actual data
- types for template functions. He provides a simple example of a template
- function which swaps two reference parameters:
- template<class T>
- void swap(T& i, T& j)
- {
- T temp = i;
- i = j;
- j = temp;
- }
- He then goes on to discuss the various roles of member functions, such as
- auxiliary roles and access roles. Another role he describes is that played by
- dormant member functions. A dormant member function is one that can be either
- active or inactive (i.e., do what it's designed to do, or do nothing) for
- different instantiated objects of the same type. C++ provides no built-in
- mechanism to make individual member functions turn themselves on or off. One
- way to add this feature to a class is to include a private integer member in
- the class definition that keeps track of a member function's activation state.
- The class's constructor initializes this integer, which is then updated when
- different actions take place upon the object. Depending on the integer's
- value, functions can either remain dormant or become active. Dormant member
- functions provide a finer granularity in manipulating objects of the same
- class.
- In this first portion of the book Shammas covers a variety of other topics,
- including purposes of static members, a survey of all the C++ operators
- (including a handy example of how the () operator can be used to do almost
- anything), the overloading of new and delete operators, nested declarations,
- template classes, and exception handling.
- Exception handling deserves further mention in this report, since it is one of
- the newest components of the language, modeled after Ada's approach. The
- example in this book illustrates how exception handling can be used to "catch"
- an out-of-bounds condition for an array class. The syntax of exception
- handling is
- try { // statements to try to execute
- }
- catch(exception) { // statements
- attempting to deal with
- // exception }
- When any statement inside the try block reaches a predefined error condition,
- that statement will raise an exception by using the keyword throw followed by
- the exception type. The catch statement will respond to the error condition.
- In Shammas's examples, the catch statement simply prints a message to cout and
- exits.
-
-
- Object Oriented Programming
-
-
- The next part of the book provides numerous examples of inheritance, ranging
- from shapes to n-dimensional objects such as matrices. The best section here
- is the discussion on object behavior. Shammas keeps things interesting by
- demonstrating real-world behavior of objects, with examples from several
- realms.
- Many people see objects as mere state holders, with the private members
- representing the state of the object. However, an object is semantically more
- than just a bunch of combinatorial Boolean values. For instance, Shammas ably
- demonstrates this with an example of an object representing a plane and its
- engines. Depending on what messages are passed to the object and its internal
- state (e.g. fuel supply), the object's "flying behavior" will change. When all
- four of its engines fail, the object invokes a private method called shutdown.
- This action effectively disables the object, modeling semantically what would
- happen in the real world. (Of course, in the real world, the plane would
- probably make an emergency landing.)
- Another, more scientific example is that of objects representing chemical
- elements and molecules. When two elements, such as hydrogen and oxygen,
- chemically react, a portion of one element usually remains which was not
- involved in the reaction. Modeling this reaction with Boolean values will not
- work. One way that will work is to give these objects weights. When a function
- is used to simulate the chemical reactions, the weights change as one object
- is completely used up while the residue of another remains. This is Shammas's
- concept of a transformed object. The capacity of an object to undergo a
- metamorphosis is what gives it real-world behavior. Shammas elegantly
- illustrates this point in the book.
-
-
- Class Templates
-
-
- Shammas's brief treatment of object behavior is a sidetrack to the main part
- of his book. The main part deals with data structures -- represented by C++
- classes holding template data. As I have not dealt with some of these
- structures since my sophomore year in college, I found this an interesting
- review. The data structures covered in this book are
- An extensive string class
- A token class which builds upon the string class
- An array class equipped with multiple sorting and searching member functions
- A list class
-
- Binary tree, AVL tree, red/black tree, and array tree classes
- A character set class
- Internal and external hash table classes
- M-Way tree and B-tree classes
- Each chapter introduces the properties of the data structure and then provides
- a possible implementation. All of the container data structures, such as
- lists, are modeled as template classes. One of the most exotic data structures
- presented is an internal hash table which uses AVL trees as entries to speed
- up searches during collisions. (An AVL tree is a height-balanced binary tree
- of sorted data entries.)
- Advanced C++ serves as a perfect model for anyone who is interested in
- creating a template class library for reusable data structures. A lot of
- people simply don't use many of the data structures they spent so much time
- studying in school. That's because many of the structures are tedious to
- implement and must be reimplemented for various data types. The real boon of
- template classes is that once they are created, they prove to be quite
- reusable with little or no modification. Generality is the programmer's dream
- come true for avoiding repetitious coding. In Shammas's examples, most of the
- class members are protected, implying that inheritance is the way to enhance
- the behavior of the class. I think Shammas does an excellent job in presenting
- his template classes and associated member functions. The only thing I don't
- like is that the examples utilizing the template data structures are too
- mundane and simple. I think it would have been more interesting if Shammas had
- combined real-world object behavior with the template data structures.
-
-
- Still Hungry
-
-
- As I said before, when I first picked up, Advanced C++, I did not expect such
- an extensive treatment of data structures. I almost think the title of the
- book should have been Advanced C++ and C++ Data Structures. If Shammas
- overemphasizes one topic, it seems to be at the expense of a few others -- for
- example, polymorphism, inheritance, and memory management.
- Some would say that polymorphism -- the ability of pointers, references, and
- member functions to take on new behaviors in derived data types at run time --
- is the foundation of object-oriented programming. Certainly, it is an
- important topic as it relates to C++. Yet Shammas does not deal with
- polymorphism in any great detail. This is in contrast to some books which
- devote much of their space to lengthy discussions of virtual functions. It may
- be that with the addition of templates, the need for polymorphism in C++ to
- simulate genericity may diminish. Nonetheless, I believe any discussion of
- advanced C++ issues should cover this topic extensively.
- Another omission -- of which almost all C++ books are guilty -- is a
- discussion on when to use multiple inheritance. This book is no exception, as
- it presents few good examples of this concept. Of course, some Smalltalk
- purists will say that it is never good practice to engage in multiple
- inheritance. However, there are times when multiple inheritance may actually
- model real-world behavior very well. For instance, in Bertrand Meyer's famous
- book, Object Oriented Software Construction, he mentions a class of drivers
- which are American and French. It makes sense for this class of drivers to
- inherit from both the American and French classes. Perhaps multiple
- inheritance leads to more complex implementations, but that does not mean
- programmers should avoid it in all cases. We need some good examples.
- Finally, in today's programming environment, object-oriented programming often
- leads to memory leaks, as this type of development usually necessitates the
- need to allocate memory for dynamic objects. Sometimes developers become
- careless and inadvertently forget to free up the space for the object once it
- is no longer needed. Although Advanced C++ is not a book about the pitfalls of
- C++, I would have liked to have seen more mention of this critical issue.
-
-
- A Good Teacher
-
-
- Shammas is a good teacher. Like all good teachers, he has organized his course
- well, first presenting advanced language components and then laying out class
- templates for data structures. (In keeping with this approach, he presents the
- most difficult data structure, the B-tree, last.)
- This is not a cookbook. Programmers looking for the proverbial 1,001 C++
- tricks will not find them here. Rather, I recommend this book for the
- intermediate C++ user because it is a very readable tutorial and can serve as
- reference for various topics. Most of the examples in the book are generic
- enough to be slightly modified to work under any modern C++ compiler. Shammas,
- an accomplished author, has provided the C++ user community a service by
- writing this book.
- Title: Advanced C++
- Author: Namir Clement Shammas
- Publisher: SAMS Publishing
- Pages: 784
- Price: $39.95
- ISBN: 0-672-30158-X
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- The Best C/C++ Tips Ever
-
-
- P.J. Plauger
-
-
- P.J. Plauger is senior editor of C/C++ Users Journal. He is convener of the
- ISO C standards committee, WG14, and active on the C++ committee, WG21. His
- latest books are The Draft Standard C++ Library, and Programming on Purpose
- (three volumes), all published by Prentice-Hall. You can reach him at
- pjp@plauger.com.
-
-
- This is a dangerous book. It purports to show 405 tips that "lead you past the
- traps and pitfalls of the language and show you how you might improve your
- programs," according to the Introduction. (I give no page numbers in this
- review because the book has nary a numbered page.) As such, it is obviously
- modeled on Effective C++, the excellent book by Scott Meyers (Addison-Wesley,
- 1992) that is organized around 50 "items." But where Meyers is always on the
- money, Porter's book short changes you most of the time. Even worse, it is
- also laced with counterfeit advice.
- Some of the tips are just plain wrong. Tip 23 tells us, "There is No Need to
- Use the enum Keyword When Declaring Instances of an enum." True enough for
- C++, perhaps, but it's in a section ostensibly aimed at C programmers. Then
- Tip 25 says, "The Equivalence Between Arrays and Pointers to Arrays Is Only
- Valid for Function Arguments." Even if you gloss over the erroneous statement
- of equivalence, the stated restriction is still untrue.
- Other tips are so badly stated as to be vacuous. Tip 68: "In Nested if
- Statements, a Dangling else Statement Goes with the Preceding if Statements."
- Where else? Or they are a matter of considerable debate, as with Tip 69: "A
- switch Statement Is More Efficient Than Nested if-else Statements." Or Tip 77:
- "It Is Faster to Use an Element Pointer Rather Than a Subscript when Scanning
- Arrays." As a general rule, if Porter makes a statement about efficiency, you
- can be reasonably certain his view is microscopic and his experience extremely
- limited. In fact, I soon learned to be wary of any generalization he makes
- about "most" computers or compilers.
- When he is not outright wrong, Porter confuses with fuzzy writing. The first
- page of tips, for example, uses the following terms for objects with dynamic
- lifetimes: functions variables [sic], function-local variables, items on the
- stack, an item declared in a function, and local items. Given the level at
- which the advice is nominally aimed, such colloquial inexactitude is
- intolerable. (I personally find it unacceptable even in advanced writings on C
- and C++.)
- He purports to give advice about standards conformance, but it is clear the
- author is vague about what's in the C Standard. Chapter 11 is called "The
- Standard C Library," and the Introduction says it covers "the ANSI C
- functions." But Tip 299 is about "Using swab" to Reverse Byte Order." That
- function is not in the Standard C library. Nor is the function putenv,
- discussed in Tip 301. Tip 302 has an archaic description of the character
- classification functions. Tip 303 misspells the tmpnam function. Tip 304 has a
- parochial description of the POSIX (not Standard C) function fstat. Tip 305
- gives erroneous information about determining file lengths. And so on, and so
- on.
- It is tempting to say that this book contains an error on every page, but I
- can't say that for sure. For one thing, C++ is sufficiently complex and
- unstandardized to date to offer lots of wiggle room. It could be that you
- could go for pages on end in that area and get advice that's as good as
- anybody else's. But I wouldn't bet the farm on it.
- The author has obviously accreted considerable lore over years of writing C
- code, primarily under MS-DOS it seems. It frightens me, however, that he can
- be so far off base in so many critical areas and still consider himself an
- expert. It saddens me that major publishers still let stuff like this get into
- print and occupy precious shelf space in book stores. It depresses me that
- thousands of innocents will still buy this book and be befuddled by such
- meretricious advice.
- Normally, I try to find some redeeming social value in any book I review. Snob
- that I am, I have to concede that a given book usually has something of value
- to somebody, even if it falls short of my lofty standards. In this case,
- however, I can't. Rather, I wish for once that Tipper Gore could push through
- mandatory warning labels for books on C and C++ that actively mislead. Kids,
- what you are seeing is performed by an untrained professional. Don't try this
- at home.
- Title: The Best C/C++ Tips Ever
- Author: Anthony Porter
- Publisher: McGraw-Hill
- Pages: approximately 500
- Price: $29.95
- ISBN: 0-07-881820-6
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Standard C/C++
-
-
- The Header <strstream>
-
-
-
-
- P.J. Plauger
-
-
- P.J. Plauger is senior editor of C/C++ Users Journal, He is convener of the
- ISO C standards committee, WG14, and active on the C++ committee, WG21. His
- latest books are The Draft Standard C++ Library, and Programming on Purpose
- (three volumes), all published by Prentice-Hall. You can reach him at
- pjp@plauger.com.
-
-
-
-
- Special Notice -- C++ Draft Public Review
-
-
- The US public review of the draft C++ standard is scheduled to begin as early
- as April 1995. For detailed information on how to get a copy of the draft and
- how to submit comments, send a message to the reflector
- c++std-notify@research.att.com. -- pjp
-
-
- Introduction
-
-
- The header <strstream> defines three classes that cooperate to help you read
- and write character sequences stored in memory (a.k.a. "string buffers"):
- strstreambuf, derived from streambuf to mediate access to an in-memory
- character sequence and grow it on demand
- istrstream, derived from istream to construct a strstreambuf object with an
- input stream and to assist in extracting from the stream
- ostrstream, derived from ostream to construct a strstreambuf object with both
- input and output streams and to assist in inserting into the stream
- An in-memory character sequence is, in many ways, the simplest kind of stream
- buffer. The buffer maintained by the controlling streambuf object is not
- merely a window on some separate representation, like an external file.
- Rather, the buffer is the character sequence in its entirety.
- Nevertheless, a strstreambuf object can control a variety of in-memory
- character sequences:
- an array of characters all of whose elements are defined from the outset
- an array of characters whose initial elements contain a null-terminated string
- an initially empty array of characters that can grow on demand, with storage
- allocated by new expressions and freed by delete expressions
- an initially empty array of characters that can grow on demand, with storage
- allocated and freed by functions supplied by the program
- All these choices are reflected in a plethora of constructors for the three
- classes defined in <strstream>.
-
-
- The Simple Choices
-
-
- Despite all the options, three simple choices stand out. You can initialize an
- istrstream object to control a constant array of characters. You can then
- extract from its associated input stream, but you cannot insert to the output
- stream. The result is effectively an istream object where you dictate the
- stream contents purely within the program. Or you can initialize an ostrstream
- object to control a non-constant array of characters. You can then insert into
- its associated output stream to store into the array.
- Your third simple choice is to initialize an ostrstream object to control an
- initially empty dynamic output stream that can grow on demand. The result is
- effectively an ostream object where you capture the stream contents purely
- within the program. You can then capture the final stream contents before the
- ostrstream object is destroyed. Class strstreambuf supplies several member
- functions to help you capture these contents. For an object x of class
- strstreambuf, here's what you can do:
- Call x.str() to obtain a pointer to the start of the controlled character
- sequence. The function freezes the racter sequence to prevent further
- insertions. Freezing also prevents the destructor for x from freeing the
- character sequence -- you assume that responsibility when you obtain the
- pointer.
- Call x.freeze() to freeze the character sequence, or x. freeze(0) to unfreeze
- it. The latter call is particularly helpful if you've learned what you need
- from an earlier x.str() call and now want the destractor to free the character
- sequence for you.
- Call x. pcount() to count the number of characters inserted in the sequence.
- The count is useful information for arbitrary character values, or if you
- don't insert a null character at the end of the character sequence.
- Here is the obvious use for the manipulator ends, by the way. (See "Standard
- C/C++: The Header <ostream>," CUJ, September 1994.) It's a clear way to supply
- a null character at the end of an in-memory character sequence.
- I end this brief introduction by mentioning the ghost at the banquet table.
- The Standard C library has long supported a similar capability. You can obtain
- formatted input from an in-memory character sequence by calling sscanf. You
- can write formatted output to an in-memory character sequence by calling
- sprintf or vsprintf. (All three functions are declared in <stdio.h>.) So how
- are these three classes any better? The answer is easy:
- An istrstream object looks like any other istream object controlling an input
- stream. And an ostrstream object looks like any other ostream object
- controlling an output stream. The older functions oblige you to know that
- you're dealing with an in memory character sequence. And you always have to
- know exactly where the sequence resides.
- Unlike sscanf, an istrstream object can control a sequence of arbitrary
- character values. It can have embedded null characters. Equally, it doesn't
- need a terminating null character.
- sprintf writes to an array of fixed length whose length is nevertheless not
- known to the function. You avoid storage overwrites only by restricting every
- single conversion specification. By contrast, an ostrstream object can control
- a sequence of known length. Insertions fail before storage overwrite occurs.
- Or the object can control a dynamic sequence of arbitrary length. Insertions
- fail only when heap storage is exhausted.
- I believe these are ample reasons to cultivate a knowledge of the header
- <strstream>.
-
-
- What the Draft Standard Says
-
-
-
- Class strstreambuf is the first of several classes derived from streambuf. (I
- describe more in future installments, on the headers <sstream> and <fstream>.)
- What makes each such stream buffer unique is the way it overrides the virtual
- member functions in the base class. The exception classes exhibit uniqueness
- in a small way, by overriding the virtual member function do_raise. (See
- "Standard C: The Header <exception>," CUJ, February 1994.) But the stream
- buffers indulge in specialization big time.
- The convention within the draft C++ Standard is to comment out virtual member
- functions in the derived class. Each such comment is labeled inherited. The
- suggestion is that an implementation need not provide an overriding definition
- if the definition in the base class does the job.
- For the classes derived from streambuf, however, these comments are often
- misleading. Listing 1, for example, shows how the draft C++ Standard defines
- the class strstreambuf. Several critical virtual member functions must have
- overriding definitions to satisfy the semantic requirements of the class. (The
- descriptions of these functions in the draft C++ Standard are often hard to
- read as well. They represent standardese at its legalistic extreme. But those
- descriptions are also the definitive word on how a class behaves in peculiar
- circumstances. Turn to them when tutorials prove inadequate.)
- Listing 2 shows how the draft C++ Standard defines the class istrstream, and
- Listing 3 shows how it defines the class ostrstream. These are derived from
- the classes istream and ostream, respectively, to facilitate operations with
- stream buffers of class strstreambuf.
-
-
- Using the Header
-
-
- You include the header <strstream) to make use of any of the classes
- istrstream, ostrstream, or strstreambuf. Objects of these classes let you read
- and write in-memory character sequences just as if they were conventional
- files. You can choose among four patterns of access:
- read only
- write only
- simple read/write
- sophisticated read/write
- I deal with each of these options in turn.
-
-
- Read-Only String Buffers
-
-
- If all you want to do is read an in-memory character sequence, construct an
- object of class istrstream to specify the character sequence and control
- extractions from it. For a null-terminated string s, you can write:
- istrstream strin(s);
- The character sequence begins at s and continues up to, but not including, the
- terminating null character that follows.
- To control an array of n characters s with arbitrary values, you can write:
- istrstream strin(s, n);
- In either case, s can be either a pointer to char or a pointer to const char.
- Whichever way you construct the istrstream object, the resultant stream buffer
- (pointed at by strin.rdbuf()) does not support insertions. You must specify an
- initial character sequence -- class istrstream has no default constructor. The
- character sequence remains unchanged for the life of the istrstream object. (I
- suggest you refrain from altering the contents of the character sequence by
- other means, if that is possible, while the istrstream object controls
- accesses to it.)
- Positioning within a read-only stream is fairly simple. A streamoff value is
- effectively an index into the character sequence. Thus:
- strin. rdbuf()->pubseekoff(0 ios::beg);
- sets the stream position at the beginning (position zero) of the character
- sequence. If the length of the sequence is nonzero, the next character
- extracted will be the first character in the sequence. Any attempt to set the
- stream position to a negative value, or beyond the end of the character
- sequence, will fail. You can also call streambuf::pubseekpos, as above, but
- that is less necessary for an in-memory character sequence. Do so for code
- that shouldn't need to know what flavor stream buffer it is really dealing
- with. (For more discussion of stream positioning, see "Standard C: The Header
- <streambuf>," CUJ, June 1994.)
- For what it's worth, you can also call strin.str() for a read-only character
- sequence. The call does not freeze the character sequence, since it is not
- dynamically alterable. The function merely returns a pointer to the beginning
- of the character sequence, just as for a character sequence you construct (as
- described below). This is always the beginning of the character sequence you
- specified when constructing the object.
- If you do call strin.str() for a read-only character sequence as above, be
- warned. The call strin.rdbuf()->pcount() will not return the length of the
- sequence. Since no output stream exists, this call returns zero. You must
- determine the length of the sequence by some other means, such as positioning
- the stream at the end and inspecting the resultant stream offset.
-
-
- Write-Only String Buffers
-
-
- If all you want to do is create an in-memory character sequence, construct an
- object of class ostrstream to control insertions into it. You can write:
- ostrstream strout;
- then insert into strout just like any other output stream. The character
- sequence can grow dynamically to arbitrary length. (The actual limit is
- usually INT_MAX, defined in <limits.h>, or when a storage allocation request
- fails.)
- If you want a null-terminated string, be sure to insert a null character last,
- as with:
- strout << ends;
- It will not be supplied for you when you call strout.str(). For a sequence of
- arbitrary characters, you can determine the number of characters you inserted
- by calling strout.pcount(). But see the warning below.)
- Positioning within a write-only stream is also fairly simple. You can work
- mostly with streamoff values, as for read-only streams described above. A few
- caveats are in order, however:
- Current practice seems to vary on where you can set the stream position. The
- safest bet is to move only to character positions with values already defined
- -- either at construction or as a result of earlier insertions.
- Some confusion is also endemic about the interaction of stream positioning
- requests and the value returned by a call such as strout.pcount().
- Even an implementation that obeys the draft C++ Standard can still surprise.
- Strictly speaking, ostrstream::pcount calls strstreambuf::pcount, which does
- not return a count of all insertions. Rather, it returns the difference
- between the current output stream position and the initial output stream
- position. The former can be left other than at the end of the character
- sequence by stream-positioning requests. The latter can be set beyond the
- beginning of the character sequence by a more sophisticated strstreambuf
- constructor, as described below.
- As usual, my advice is to avoid such delicate areas in code you hope to keep
- portable. If you insist on pushing the limits of clearly defined behavior,
- expect surprises.
- Your goal in creating a write-only character sequence is to capture the final
- result, as a rule. Call strout.str() to freeze the character sequence and
- return a pointer to its initial character. Remember that freezing the
- character sequence tells the strstreambuf destructor not to free storage for
- the character sequence. You must either free the storage yourself or be sure
- to call strout.freeze(0) before strout is destroyed.
-
-
- Simple Read/Write String Buffers
-
-
- If you want to create an in-memory character sequence that you can read as
- well as write, you need two objects to control the input and output streams.
- First, construct an object of class ostrstream to supply the strstreambuf
- object and control insertions, then construct an object of class istream to
- control extractions. You can write:
- ostrstream strout;
- istream strin(strout. rdbuf());
- much as I have described several times in earlier installments for setting up
- a bidirectional stream. (See, for example, "Standard C: Introduction to
- Iostreams," CUJ, April 1994.) You can then insert into strout just like any
- other output stream. And once you have inserted characters, you can extract
- them from strin just like any other input stream.
- A few caveats are in order, however. Try to extract beyond the last character
- in the sequence and you will encounter end-of-file. Once you extend the
- sequence by inserting characters, you can successfully extract again, but not
- until you clear eofbit if it is set in the istream object. Once again, this
- status bit proves less than trustworthy.
- You can position the input and output streams separately or jointly. As usual,
- positioning operations demand a modicum of caution:
-
- The draft C++ Standard specifies a default third argument value which =
- ios::in ios::out for streambuf::pubseekoff. (It specifies the same for the
- default second argument to streambuf::pubseekpos.) Thus, unless you supply an
- actual which argument that selects only one stream -- ios::in or ios::out -- a
- call to this member function endeavors to position both streams in tandem.
- Seldom does that make sense. When both reading and writing an in-memory
- character sequence, always specify the which argument on such calls.
- The draft C++ Standard is even nastier at times. If the second argument to
- streambuf::pubseekof is ios::cur, a tandem positioning operation will fail.
- For such relative positioning requests, you should always specify a which
- argument that selects only one stream.
- Note that the second caveat applies even to read-only or write-only in-memory
- character sequences.
- As an important aside, here is a warning regarding the member function rdbuf.
- Several classes in the Standard C++ library are derived from either istream or
- ostream. These include istrstream and ostrstream, in this particular header.
- All such derived classes also provide a member function rdbuf() that hides the
- member function in the base class ios.
- Why is this so? All such derived classes also provide a member object of a
- class derived from streambuf. In this header, that derived class happens to be
- strstreambuf. Consider, for example, the call istr.rdbuf(), for an object istr
- of class istrstream. It returns a pointer to the strstreambuf member object in
- istr. And the return type is indeed pointer to strstreambuf, not pointer to
- streambuf as in the base class ios.
- A generic pointer to the base streambuf gets you to all the inherited member
- functions. It even gets you to the proper overriding definitions of virtual
- member functions. But to access any member functions peculiar to the derived
- class, you need such a specialized pointer. Again in this particular case, the
- member functions freeze, pcount, and str are peculiar to class strstreambuf,
- not its base class.
- Potential confusion arises, however, once you make a call such as
- istr.rdbuf(strout.rdbuf()). The critical pointer in the ios subobject now
- designates a different stream buffer. But a subsequent call to istr.rdbuf()
- still returns a pointer to the member object within istr. To get the pointer
- actually used by inserters and extractors, you must make the hairy call
- ((ios&)istr).rdbuf().
- Got all that? If not, don't worry too much about it. The bottom line is that
- you should follow a fundamental style rule. Alter the stored stream buffer
- pointer only in an object of class istream or ostream. Never do so in an
- object of a class derived from one of these. Even better, do as I did above --
- use a special istream or ostream object constructed from the outset to share
- an existing stream buffer. With either approach, there's never a second stream
- buffer lying around to cause confusion.
-
-
- Sophisticated String Buffers
-
-
- You can get even fancier with the classes defined in <strstream>. What follows
- is an assortment of more sophisticated setups.
- Say you want to use an existing character array to store a character sequence,
- and you want to read the character sequence once you write it. If you declare:
- ostrstream strout(s, n);
- istream strin(strout.rdbuf());
- then strout controls the sequence of n characters beginning at s. Both the
- read position and the write position are at the beginning of the character
- sequence. In this case, you cannot extend the sequence by inserting at or
- beyond character position n.
- Here's a variation on this scenario. Say you want to use an existing character
- array to store a character sequence, and the array already stores a
- null-terminated string. You want to begin extracting from the beginning of the
- character sequence, but you want to begin inserting at the end of the
- null-terminated string (first replacing the terminating null). If you declare:
- ostrstream strout(s, n, ios::app);
- istream strin(strout.rdbuf());
- then you get the desired behavior. Insertions effectively append characters to
- an initial null-terminated string, but won't continue past the end of the
- character array. For extractions, see the caveat above about the behavior of
- eofbit.
- If you want to get fancier, you have to construct a strstreambuf object
- directly. This class has lots of constructors with lots of options. I describe
- here just the two that I feel are genuinely useful and safe.
- Say you want to construct an in-memory character sequence that you know will
- get very large. You'd like to suggest, when constructing the strstreambuf
- object, that the object allocate space for a large sequence right off the
- mark. That can save lots of execution overhead in reallocating storage as the
- character sequence grows. Equally, you might want to construct a number of
- character sequences all of which will be fairly small. You'd like to suggest
- that each such object allocate only a small number of characters. That can
- save lots of unused storage. If you declare:
- strstreambuf sb(n);
- istream strin(&sb);
- ostream strout(&sb);
- for some int value n, the constructor should take the hint. What it does with
- it is up to the implementation, but at least you get to make the suggestion.
- Another approach to storage allocation is to do the job yourself. So your
- final interesting choice is to begin with two functions like:
- #include <stdlib. h>
-
- void *my_alloc(size_t n)
- { // allocate n chars
- return (malloc(n));
- }
-
- void my_free(void *p)
- { // free allocated storage
- free(p);
- }
- Tailor these basic functions as you see fit. Then you can declare:
- strstreambuf sb(&my_alloc,
- &my_free);
- istream strin(&sb);
- ostream strout(&sb);
- The two functions you supply will be called, in place of new and delete
- expressions, to allocate and free storage for character sequences.
-
-
- Next Month
-
-
- That's a capsule summary of how to use string buffers, as specified by the
- draft C++ Standard. Next month, I'll show you one way to implement this
- header, and discuss some of the interesting challenges it presents.
- This article is excerpted in part from P.J. Plauger, The Draft Standard C++
- Library, (Englewood Cliffs, N.J.: Prentice-Hall, 1995.)
-
- Listing 1 The class strstreambuf
- class strstreambuf : public streambuf {
- public:
- strstreambuf(int alsize_arg: 0);
- strstreambuf(void* (*palloc_arg)(size_t),
-
- void (*pfree_arg)(void*));
- strstreambuf(char* gnext_arg. int n. char* pbeg_arg = 0);
- strstreambuf(unsigned char* gnext arg. int n,
- unsigned char* pbeg_arg = 0);
- strstreambuf(signed char* gnext_arg, int n,
- signed char* pbeg_arg = 0);
- strstreambuf(const char* gnext_arg, int n);
- strstreambuf(const unsigned char* gnext_arg, int n);
- strstreambuf(const signed char* gnext_arg. int n);
- virtual ~strstreambuf();
- void freeze(int = 1);
- char* str();
- int pcount();
- protected:
- // virtual int overflow(int c = EOF); inherited
- // virtual int pbackfail(int c = EOF); inherited
- // virtual int underflow(); inherited
- // virtual int uflow(); inherited
- // virtual int xsgetn(char* s. int n); inherited
- // virtual int xsputn(const char* s. int n); inherited
- // virtual streampos seekoff(streamoff off, ios::seekdir way,
- // ios::openmode which = ios::in ios::out); inherited
- // virtual streampos seekpos(streampos sp,
- // ios::openmode which = ios::in ios::out); inherited
- // virtual streambuf* setbuf(char* s. int n); inherited
- // virtual int sync(); inherited
- private:
- // typedef T1 strstate; exposition only
- // static const strstate allocated; exposition only
- // static const strstate constant; exposition only
- // static const strstate dynamic; exposition only
- // static const strstate frozen; exposition only
- // strstate strmode; exposition only
- // int alsize; exposition only
- // void* (*palloc)(size_t); exposition only
- // void (*pfree)(void*); exposition only
- };
-
-
- Listing 2 The class istrstream
- class istrstream: public istream {
- public:
- istrstream(const char* s);
- istrstream(const char* s, int n);
- istrstream(char* s);
- istrstream(char* s, int n);
- virtual ~istrstream();
- strstreambu* rdbuf() const;
- char *str();
- private;
- // strstreambuf sb; exposition only
- };
-
-
- Listing 3 The class ostrstream
- class ostrstream: public ostream {
- public:
- ostrstream();;
- ostrstream(char* s, int n, openmode mode = out);
-
- virtual ~ostrstream();
- strstreambuf* rdbuf() const;
- void freeze(int freezel = 1);
- char *str();
- int pcount() const;
- private;
- // strstreambuf sb; exposition only
- };
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Code Capsules
-
-
- The Standard C Library, Part 1
-
-
-
-
- Chuck Allison
-
-
- Chuck Allison is a regular columnist with CUJ and a Senior Software Engineer
- in the Information and Communication Systems Department of the Church of Jesus
- Christ of Latter Day Saints in Salt Lake City. He has a B.S. and M.S. in
- mathematics, has been programming since 1975, and has been teaching and
- developing in C since 1984. His current interest is object-oriented technology
- and education. He is a member of X3J16, the ANSI C++ Standards Committee.
- Chuck can be reached on the Internet at 72640.1507@compuserve.com.
-
-
- Although it may not seem like it, C is a very small language. In fact, it was
- first implemented on a very small platform by today's standards. The first C
- compiler I owned ran on a Commodore 64! C's simplicity and compactness make it
- ideal for systems programming and for developing programs that run in embedded
- systems, such as in automobiles or cameras.
- A key difference between C in such freestanding environments and C in a hosted
- environment, such as a desktop or mid-range computer, is the presence of the
- Standard C library. In a freestanding environment a conforming compiler only
- needs to provide the types and macros specified in <float.h>, <limits.h>,
- <stdarg.h>, and <stddef.h>. By contrast, in hosted environments, programmers
- who work on typical data processing projects take the library for granted --
- in fact, they think of it as part of the language. A large portion of everyday
- C code consists of library calls. Even I/O facilities like printf and scanf
- are part of the library, not the language.
- The Standard C library consists of functions, type definitions, and macros
- declared in fifteen header files. Each header more or less represents a domain
- of programming functionality, such as I/O or string processing operations.
- Some macros and type definitions, such as NULL and size_t, appear in more than
- one header file for convenience.
- In this article I divide the Standard library into three groups (see Table 1
- through Table 3). Group I represents library components that you should
- understand thoroughly if you want to consider yourself a C programmer. Too
- often I have seen programs that "reinvent" basic library facilities such as
- memcpy or strchr. Such programs should not be executed, but sometimes I think
- their programmers should be (or at least fired). To receive your paycheck in
- good conscience, though, you should really master Group II as well. And
- although you may need the functions in Group III only once in a blue moon, you
- should be familiar enough with them that you know how to use them when that
- need arises.
- I certainly don't plan to review the entire library in this article. If you
- want a comprehensive reference, there is nothing better than Plauger's book,
- The Standard C Library [1]. For tips, tricks, and tutorials, you can read most
- of the back issues of this column (which started in October, 1992). What I
- will do here is bring to light some of the library functions you may have
- overlooked, and some behavior you may not be aware of.
-
-
- Group I -- For the "Adequate" C Programmer
-
-
-
-
- <ctype. h>
-
-
- The functions in <ctype.h> support typical operations for handling single
- characters (see Table 4). For example, to determine if a character C is upper
- case, use the expression isupper(c). Many old-time C programs are peppered
- with expressions such as
- ('A' <= c && c <= 'Z')
- instead, which makes poor reading. Putting such an expression in a macro
- helps, as in
- #define ISUPPER(c) ('A' <= c & c <= 'Z')
- But this makes expressions with side effects (such as ISUPPER(c++))
- unreliable. And of course this test for uppercase membership works only with a
- character set that encodes the alphabet contiguously, such as ASCII. By
- contrast, the character classification functions in <ctype.h> are safe and
- portable across all platforms.
- It is important not to assume that ASCII is always the execution character
- set. For example, ASCII control characters comprise the code 127 and those
- less than 32, but only seven control characters behave uniformly across all
- environments: alert ( '\a'), backspace ( '\b'), carriage return ( '\r'), form
- feed ( '\f''), horizontal tab ( '\t'), newline ( '\n'), and vertical tab (
- '\v'). The only character-handling functions that do not change behavior when
- you change locale are isdigit and isxdigit.(See Group III next month for more
- on locales).
- Although you can assume that the digits '0' through '9' have contiguous codes
- in all C execution character sets, the hexadecimal digits, being alphabetic
- characters, do not. The function atox in Listing 1 shows how to convert a
- hexadecimal string to an integer value. Unfortunately, it only works for
- ASCII-like character sets. The offending line is:
- digit= toupper(*s) - 'A' + 10;
- There is no guarantee that the expression
- toupper(*s) - 'A'
- will give the correct result. The version in Listing 2 works on any platform
- because it stores all hexadecimal digits contiguously in its own array. It
- searches the array with strchr and then uses pointer arithmetic to compute the
- value of the digit.
-
-
- <stdio.h>
-
-
- The author of Listing 1 and Listing 2 could have avoided a lot of trouble if
- he had only understood scanf formatting a little better. As Listing 3
- illustrates, the "%x" edit descriptor does all the work of reading hexadecimal
- numbers for you. Unlike the previous two versions, it even handles a leading
- plus or minus sign. Both scanf and printf are laden with features that so many
- programmers overlook. For more detail on these two functions, see the Code
- Capsules in the October 1992 and November 1992 issues of CUJ.
- The printf/scanf families of functions shown in Table 5 perform formatted I/0.
- Furthermore, they provide these facilities for three types of streams:
- standard streams, file streams, and string (i.e., in-core) streams. Formatting
- operates identically on the different types of streams, but of necessity the
- function names and calling sequences are somewhat different.
- The <stdio.h> component of the Standard C library provides two other classes
- of input/output facilities: character I/0 and block I/0 (see Table 6 and Table
- 7). The functions in Listing 4 and Listing 5 copy one file to another using
- character I/0 and block I/0 functions respectively. Note that since fread does
- not return an error code, I must make an explicit call to ferror to detect a
- read error.
- As Table 7 illustrates, <stdio.h> provides functions for file positioning. The
- time-worn functions fseek and ftell work reliably only on files opened in
- binary mode, and are limited to file positions that can be represented by a
- long integer. To overcome these limitations, the ANSI committee invented
- fgetpos and fsetpos, which use the abstract type fpos_t (Table 8) as a file
- position indicator.
- The program in Listing 6 puts fgetpos and fsetpos to good use in a simple
- four-way scrolling browser for large files. The browser keeps only one
- screen's worth of text in memory. If you want to scroll up or down through the
- file, it reads (or re-reads) the adjacent text and displays it. When scrolling
- down (i.e., forward) through the file, the program pushes the file position
- corresponding to the old screen data on a stack, and reads the next screenful
- from the current file position. To scroll up, the program retrieves the file
- position of the previous screen from the stack. For the complete program, and
- for more detailed information about file I/0, see the Code Capsule in the May
- 1993 edition of CUJ.
-
-
- <stdlib.h>
-
-
- The header <stdlib.h> is a bit of a catch-all. It defines types, macros, and
- functions for memory management, sorting and searching, integer arithmetic,
- string-to-number conversions, sequences of pseudorandom numbers, interfacing
- with the environment, and converting multi-byte strings and characters to and
- from wide character representations (see Table 9). The program in Listing 7
- uses all four memory management functions to sort a text file. Whenever its
- array of pointers to char fills up, it expands it with realloc, which
- preserves the original contents. See "Code Capsules: Dynamic Memory
- Management, Part 1," CUJ, October 1994, for an in-depth treatment of memory
- management in C. For more information on the sort function qsort, see the Code
- Capsule "Sorting with qsort," CUJ, April 1993. The search function, bsearch,
- searches a sorted list for a given key. Like qsort, you supply bsearch with a
- compare function and it returns a pointer to the array element containing the
- key (see Listing 8).
- The program in Listing 9 illustrates some of <stdlib.h>'s seldom-used
- functions. It shuffles a deck of 52 cards by creating a randomized sequence of
- the numbers 0 through 51. The srand function seeds the pseudo-random generator
- by encoding the current time and date. To derive the suit and denomination
- from a number, I divide the number by 13, the number of cards in each suit.
- The remainder of this division is the denomination (0 through 12 corresponding
- to ace through king), and the quotient represents the suit as follows:
-
- 0 == clubs
- 1 == diamonds
- 2 == hearts
- 3 == spades
- The div function computes the quotient and remainder all at once and stores
- the result in a structure of type div_t..
- The functions in the scanf family call strtol to convert character strings to
- integers. Using strtol directly, however, you can read numbers in any base
- from 2 to 35, as the program in Listing 10 illustrates. strtol updates nextp
- through its second argument so you can progress through the string, converting
- one number after another. The functions strtoul and strtod behave similarly
- for unsigned longs and doubles respectively. With strtol, I can write a
- superior version of the atox conversion function, as shown in Listing 11.
- Everyone reading this column is probably familiar with the functions exit and
- abort. You may not know, however, that you can "register" functions to be
- called automatically at program exit. These functions are usually called "exit
- handlers" and you register them with the atexit function, like this:
- void my_handler();
- atexit(my_handler);
- An exit handler must take no arguments and must return void. Upon normal exit
- (i.e., return from main or a call to exit), C calls all of your handlers in
- the reverse of the order that they were registered. You can register up to 32
- exit handlers.
- The getenv function allows you to query strings in your host environment. For
- example, to find the current setting of the PATH variable, which is common to
- many environments, you can do the following:
- char *path = getenv("PATH");
- The pointer refers to memory outside of your program, so if you want to keep
- the value, you'll have to copy it to a program variable before the next call
- to getenv.
-
-
- <string. h>
-
-
- The functions defined in <string.h> are shown in Table 10. All the functions
- with the str prefix work on null-terminated strings, and the mem-functions
- process raw memory. You've already seen strchr in Listing 2, and its companion
- memchr in Listing 9. To transfer raw bytes from one location to another, use
- memcpy, or memmove, if the source and destination buffers overlap. (Judging by
- the number of times I've seen memcpy reinvented in others' code, I believe
- that it is the most overlooked function in the standard library).
- The string search functions also go too often unused. The program in Listing
- 12 uses strstr to extract all lines from a text file that contain a given
- string. Due to their cryptic names, the following three <string.h> functions
- are probably the least used:
- 1) size_t strspn(char *s1, char *s 2); "Spans" the characters from s2
- occurring in s1. In plain English, strspn returns the index of the first
- character in s1 which is not in s2.
- 2) size_t strcspn(char *s1, char *s2); "Spans" the characters not in s2
- occurring in s1. In other words, strcspn returns the index of the first
- character in s1 that is also in s2.
- 3) char *strpbrk(char *s1, char *s2); Returns a pointer to the first character
- from s2 that occurs in s1. It's kind of like a cross between strchr and
- strcspn.
- The program in Listing 13 illustrates these functions. For more on string
- handling, see the "Code Capsule" in the December 1992 issue (vol. 10, no. 12).
-
-
- Summary
-
-
- Although you may not agree totally with my categorization of Standard C
- library components, I hope I have caused you to think about your own practices
- and level of expertise. Whatever our opinions, one bit of advice is
- indisputably in order: know and use the Standard library! Next month I'll
- cover Group II.
-
-
- Further Reading
-
-
- [1] Plauger, P. J. The Standard C Library. Prentice-Hall, 1992. ISBN
- 0-13-131509-9.
- Table 1 Standard C Headers: Group I (required knowledge for every C
- programmer)
- <ctype.h> Character Handling
-
- <stdio.h> Input/Output
-
- <stdlib.h> Miscellaneous Utilities
-
- <string.h> Text Processing
- Table 2 Standard C Headers: Group II (tools for the professional)
- <assert.h> Assertion Support for Defensive Programming
-
- <limits.h> System Parameters for Integer Arithmetic
-
- <stddef.h> Universal Types & Constants
-
- <time.h> Time Processing
- Table 3 Standard C Headers: Group III (power at your fingertips when you need
- it)
- <errno.h> Error Detection
-
- <float.h> System Parameters for Real Arithmetic
-
- <locale.h> Cultural Adaptation
-
-
- <math.h> Mathematical Functions
-
- <setjmp.h> Non-local Branching
-
- <signal.h> Interrupt Handling (sort of)
-
- <stdarg.h> Variable-length Argument Lists
- Table 4 <ctype.h> functions
- Character Testing Functions
- -----------------------------------------------------
- isalnum alphanumeric ( isalpha isdigit)
-
- isalpha alphabetic
-
- iscntrl control (beware!)
-
- isdigit '0' through '9'
-
- isgraph visible when printed
-
- islower lower case alphabetic
-
- isprint isgraph ' '
-
- ispunct isgraph && !isalnum
-
- isspace whitespace
-
- isupper upper case alphabetic
-
- isxdigit isdigit 'a' thru 'f' 'A' thru 'F'
-
- Character Mapping Functions
- -----------------------------------------------------
- tolower convert to lower case (if applicable)
-
- toupper convert to upper case (if applicable)
- Table 5 Functions for Formatted I/0 defined in <stdio.h>
- Fixed-length argument lists
- --------------------------------
- scanf (standard input)
-
- fscanf (file input)
-
- sscanf (in-core input)
-
- printf (standard output)
-
- fprintf (file output)
-
- sprintf (in-core output)
-
- Variable-length argument lists
- ----------------------------------
- vprintf (standard output)
- vfprintf (file output)
- vsprintf (in-core output)
- Table 6 Character I/O Functions in <stdio.h>
- Single-character Processing Functions
-
- -------------------------------------
- getchar (standard input)
-
- getc (file input)
-
- ungetc (affects file input)
-
- fgetc (file input)
-
- putchar (standard output)
-
- putc (file output)
-
- fputc (file output)
-
- String Processing Functions
- -------------------------------------
- gets (standard input)
-
- fgets (file input)
-
- puts (standard output)
-
- fputs (file output)
- Table 7 Other <stdio.h> Functions
- Block I/0
- -----------------------
- fread
- fwrite
-
- Operations via Filename
- -----------------------
- remove
- rename
-
- Temporary Files
- -----------------------
- tmpfile
- tmpnam
-
- File Access Functions
- -----------------------
- fopen
- freopen
- fclose
- fflush
- setbuf
- setvbuf
-
- File Positioning
- -----------------------
- fgetpos
- fsetpos
- fseek
- ftell
- rewind
-
- Error Handling
- -----------------------
-
- clearerr
- feof
- ferror
- Table 8 Types and macros defined in <stdio.h>
- Types
- ------------------------------------------------------------
- FILE Encapsulates file access info
- fpos_t File Position (returned by fgetpos)
-
- Macros
- ------------------------------------------------------------
- NULL Zero pointer
- EOF Special value representing end-of-file
- BUFSIZ Preferred stream buffer size
- FOPEN_MAX Max # of files open simultaneously
- FILENAME_MAX Max # of characters in a file name minus 1
- L_tmpnam Max # of characters in a tempfile name minus 1
- TMP_MAX Max # of distinct filenames returned from
- tmpnam
- SEEK_CUR Signals fseek to seek relative to current position
- SEEK_END Signals fseek to seek from end-of-file
- SEEK_SET Signals fseek to seek from start-of-file
- Table 9 <stdlib.h> Declarations
- Types
- --------------------------------------------------------
- div_t (structure returned by div)
- ldiv_t (structure returned by ldiv)
-
- Constants
- --------------------------------------------------------
- NULL
- EXIT_FAILURE (Portable error code for exit)
- EXIT_SUCCESS (Portable success code for exit)
- RAND_MAX (Max value returned by rand)
- MB_CUR_MAX (Max # of bytes in a multi-byte character)
-
- String Conversion Functions
- --------------------------------------------------------
- atof strtod
- atoi strtol
- atol strtoul
-
- Random number Functions
- --------------------------------------------------------
- rand (Returns the next pseudo-random number)
- srand ("Seeds" the sequence of pseudo-random
- numbers)
-
- Memory Management
- --------------------------------------------------------
- calloc realloc
- malloc free
-
- Interface to the Environment
- --------------------------------------------------------
- abort getenv
- atexit system
- exit
-
-
- Searching and Sorting
- --------------------------------------------------------
- bsearch
- qsort
-
- Integer Arithmetic
- --------------------------------------------------------
- abs labs
- div ldiv
-
- Multibyte Character Functions
- --------------------------------------------------------
- mblen mbctowcs
- mbtowc wcstombs
- wctomb
- Table 10 Functions defined in <string.h>
- Copying
- ------------------------------------
- memcpy strcpy
- memmove strncpy
-
- Concatenation
- ------------------------------------
- strcat
- strncat
-
- Comparison
- ------------------------------------
- memcmp strncmp
- strcmp strxfrm
- strcoll
-
- Search Functions
- ------------------------------------
- memchr strrchr
- strchr strspn
- strcspn strstr
- strpbrk strtok
-
- Miscellaneous
- ------------------------------------
- memset
- strerror
- strlen
-
- Listing 1 Converts a hex-string to a number in ASCII environments
- #include <ctype.h>
- #include <assert.h>
-
- long atox(char *s)
- {
- long sum;
-
- assert(s);
-
- /* Skip whitespace */
- while (isspace(*s))
- ++s;
-
-
- /* Do the conversion */
-
- for (sum = 0L; isxdigit(*s); ++s)
- {
- int digit;
-
- if (isdigit(*s))
- digit = *s - '0';
- else
- digit = toupper(*s) - 'A' + 10;
- sum = sum*16L + digit;
- }
-
- return sum;
- }
- /* End of File */
-
-
- Listing 2 A portable version of Listing 1
- #include (ctype.h>
- #include <assert.h>
- #include <string.h>
-
- long atox(char *s)
- {
- char xdigs[] = "012345679ABCDEF":
- long sum;
-
- assert(s);
-
- /* Skip whitespace */
- while (isspace(*s))
- ++s;
-
- /* Do the conversion */
- for (sum = 0L: isxdigit(*s); ++s)
- {
- int digit = strchr(xdigs,toupper(*s)) - xdigs;
- sum = sum*16L + digit;
- }
-
- return sum;
- }
- /* End of File */
-
-
- Listing 3 Converts a hex-string to a number via sscanf
- #include <stdio.h>
-
- long atox(char *s)
- {
- long n = 0L;
- sscanf( s, "%x", &n );
- return n;
- }
-
- /* End of File */
-
-
-
- Listing 4 A Function that copies a file via character I/0
- /* copy1.c */
- #include <stdio.h>
-
- int copy(FILE *dest, FILE *source)
- {
- int c;
-
- while ((c = getc(source)) != EOF)
- if (putc(c,dest) == EOF)
- return EOF;
- return 0;
- }
-
- /* End of File */
-
-
- Listing 5 A Function that copies a file via block I/0
- /* copy2.c */
- #include <stdio.h>
-
- int copy(FILE *dest, FILE *source)
- {
- size_t count;
- static char buf[BUFSIZ];
-
- while (!feof(source))
- {
- count = fread(buf,l,BUFSIZ,source);
- if (ferror(source))
- return EOF;
- If (fwrite(buf,l,count,dest) != count)
- return EOF;
-
- }
- return 0;
- }
-
- /* End of File */
-
-
- Listing 6 Outline of a file viewing program that illustrates file positioning
- /* view.c: A simple 4-way-scrolling file browser */
-
- /* cls(), display(), read_a_screen() omitted...*/
-
- main(int argc, char *argv[])
- {
- fpos_t top_pos, stk_[MAXSTACK];
- /* Details omitted...*/
-
- top:
- /* Display initial screen */
- rewind(f);
- fgetpos(f,&top_pos);
- /* Details omitted...*/
-
- for (;;)
- {
-
- switch(c = toupper(getchar()))
- {
- case 'D': /* Display the next screen */
- if (!feof(f))
- {
- PUSH(top_pos);
- fgetpos(f,&top_pos);
- read_a_screeen(f);
- display(file);
- }
- break;
-
- case 'U': /* Display the previous screen */
- if (stkptr_> 0)
- {
- top_pos = POP();
- fsetpos(f,&top_pos);
- read_a_screen(f);
- display(file);
- }
- break;
- case 'T': /* Display last screen */
- stkptr_ = 0;
- goto top;
-
- case 'B': /* Display last screen */
- while (!feof(f)
- {
- PUSH(top_pos);
- fgetpos(f,&top_pos);
- read_a_screen(f)
- }
- display(file);
- break;
-
- case 'Q': /* Quit */
- cls();
- return EXIT_SUCCESS;
- }
- /* Details omitted...*/
- }
- }
- /* End of file */
-
-
- Listing 7 Sorts files as large as available memory
- /* sort.c */
- #include <stdio.h>
- #include <assert.h>
- #include <stdlib.h>
- #include <string.h>
-
- #define MAXLINES 512
-
- int comp(const void *, const void *);
-
- main()
- {
- int i;
-
- size_t nlines, maxlines = MAXLINES;
- static char s[BUFSIZ];
- char **lines = calloc(sizeof(char *), maxlines);
-
- /* Read file */
- for (nlines = 0; fgets(s,BUFSIZ,stdin); ++nlines)
- {
- if (nlines == maxlines)
- {
- /* Expand array of strings */
- maxlines += MAXLINES;
- lines = realloc(lines,maxlines*sizeof(char *));
- assert(lines);
- }
-
- /* Store this line */
- lines[nlines] = malloc(strlen(s)+1);
- assert(lines[nlines]);
- strcpy(lines[nlines],s);
- }
-
- /* Sort */
- qsort(lines,nlines,sizeof lines[0],comp);
-
- /* Print / free memory */
- for (i = 0; i < nlines; ++i)
- {
- fputs(lines[i],stdout);
- fflush(stdout);
- assert(!ferror(stdout));
- free(lines[i]);
- }
- free(lines);
- return 0;
- }
-
- /* Compare function for qsort(): */
- int comp(const void *pl, const void *p2)
- {
- return strcmp(* (char **) pl, * (char **) p2);
- }
-
- /* End of File */
-
-
- Listing 8 Searches a sorted array of records with the bsearch function
- /* search.c */
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
-
- struct person
- {
- char last[16];
- char first[11];
- char phone[13];
- int age;
- };
-
-
- static int comp(const void *, const void *);
-
- main()
- {
- int i;
- struct person *p;
- static struct person key = {"","","555-1965",0};
- static struct person people[] =
- {{"Ford","Henry","555-1903",98},
- {"Lincoln","Abraham","555-1865",161},
- {"Ford","Edsel","555-1965",53},
- {"Trump","Donald","555-1988",49}};
-
- /* Sort */
- qsort(people, 4, sizeof people[0], comp);
-
- /* Search */
- p = bsearch(&key, people, 4, sizeof people[0], comp);
- if (p != NULL)
- {
- printf(
- "%s, %s, %s, %d\n",
- p->last,
- p->first,
- p->phone,
- p->age
- );
- }
- else
- puts("Not found");
- return 0;
- }
-
- /* Compare function: */
- static int comp(const void *x, const void *y)
- {
- struct person *pl = (struct person *) x;
- struct person *p2 = (struct person *) y;
-
- return strcmp(p1->phone,p2->phone);
- }
-
- /* Output: */
- Ford, Edsel, 555-1965, 53
-
- /* End of File */
-
-
- Listing 9 A card shuffling program that illustrates the random number and
- integer division functions in <stdlib.h>
- /* deal.c: Deal a hand from a shuffled deck of cards */
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <time.h>
-
- #define DECKSIZE 52
- #define SUITSIZE 13
-
- main(int argc, char *argv[])
-
- {
- int ncards = DECKSIZE; /* Deal full deck by default */
- char deck[DECKSIZE]; /* An array of small integers */
- size_t deckp;
- unsigned int seed;
-
- /* Get optional hand size */
- if (argc > 1)
- if ((ncards = abs(atoi(argv[1])) % DECKSIZE) == 0)
- ncards = DECKSIZE;
-
- /* Seed the random number generator */
- seed = (unsigned int) time(NULL);
- srand(seed);
-
- /* Shuffle */
- deckp = 0;
- while (deckp < ncards)
- {
- int num = rand() % DECKSIZE;
- if (memchr(deck, num, deckp) == NULL)
- deck[deckp++] = (char) num;
- }
-
- /* Deal */
- for (deckp = 0; deckp < ncards; ++deckp)
- {
- divt_card = div(deck[deckp], SUITSIZE);
- printf(
- "%c(%c)%c",
- "A23456789TJQK"[card.rem],
- "CDHS" [card.quot],
- (deckp+1) % SUITSIZE ? ' ' : '\n'
- );
- }
-
- return 0;
- }
-
- /* Output: */
- A(C) 6(S) 7(C) 9(C) 3(H) 6(C) 8(D) 3(C) 6(D) 5(D) 2(H) A(S) 4(H)
- 8(C) 8(H) 6(H) J(S) 7(S) Q(C) 2(C) Q(H) K(H) 4(C) 5(S) T(H) Q(S)
- 9(H) T(D) T(S) 9(D) K(C) 3(S) J(C) 5(C) T(C) K(S) 7(D) 2(D) 4(S)
- 8(S) 5(H) A(D) 7(H) 3(D) Q(D) A(H) 2(S) J(D) 9(S) K(D) J(H) 4(D)
-
- /* End of File */
-
-
- Listing 10 Uses strtol() to read numbers in different bases
- #include <stdio.h>
- #include <stdlib.h>
-
- main()
- {
- char *input = "101 123 45678 90abc g";
- char *nextp = input;
- long bin, oct, dec, hex, beyond:
-
- bin = strtol(nextp,&nextp,2);
-
- oct = strtol(nextp,&nextp,8);
- dec = strtol(nextp,&nextp,10);
- hex = strtol(nextp,&nextp,16);
- beyond = strtol(nextp,&nextp,17);
-
- printf("bin = %ld\n",bin);
- printf("oct =
- %lo\n",oct);
- printf("dec = %ld\n",dec);
- printf("hex = %lx\n",hex);
- printf("beyond = %ld\n",beyond);
- return 0;
- }
-
- /* Output: */
- bin = 5
- oct = 123
- dec = 45678
- hex = 90abc
- beyond = 16
-
- /* End of File */
-
-
- Listing 11 An even better version of atox() using strtol()
- #include <stdlib.h>
-
- long atox(char *s)
- {
- return strtol(s, NULL, 16);
- }
-
- /* End of File */
-
-
- Listing 12 Uses strstr to find substrings
- /* find.c: Extract lines from a file */
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
-
- main(int argc, char *argv[])
- {
- char line[BUFSIZ];
- char *search_str;
- int lineno = 0;
-
- if (argc == 1)
- return EXIT_FAILURE;
- /* Search string required */
- else
- search_str = argv[l];
-
- while (gets(line))
- {
- ++lineno;
- if (strstr(line,search_str))
- printf("%d: %s\n",lineno,line);
-
- }
-
- return EXIT_SUCCESS;
- }
-
- /*Results from the command
- "find str <find.c": */
- 5: #include <string.h>
- 12: char *search_str;
- 16: return EXIT_FAILURE;
- 18: search_str = argv[l];
- 23: if (strstr(line,search_str))
-
- /* End of File */
-
-
- Listing 13 Illustrates selected string search functions
- #include <stdio.h>
- #include <string.h)
-
- void display_span(char *, int);
- main()
- {
- char *s = "Eeek! A mouse device!;
- char *vowels = "AEIOUaeiou";
- char *punct =
- "'~!@#$%^&*()-_=+\\[{]};:'\",<.>/?";
- char *ptr;
-
- display_span(s,strspn(s,vowels));
- display_span(s,strspn(s,punct));
- display_span(s,strcspn(s,vowels));
- display_span(s,strcspn(s,punct));
-
- ptr = strpbrk(s,vowels);
- puts(ptr);
- ptr = strpbrk(s,punct);
- puts(ptr);
-
- return 0;
- }
-
- void display_span(char *s, size_t index)
- {
- printf("%d characters spanned: %.*s\n",
- index,index,s);
- }
-
- /* Output: */
- 3 characters spanned: Eee
- 0 characters spanned:
- 0 characters spanned:
- 4 characters spanned: Eeek
- Eeek! A mouse device!
- ! A mouse device!
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Stepping Up To C++
-
-
- C++ at CD Registration
-
-
-
-
- Dan Saks
-
-
- Dan Saks is the president of Saks & Associates, which offers consulting and
- training in C++ and C. He is secretary of the ANSI and ISO C++ committees. Dan
- is coauthor of C++ Programming Guidelines, and codeveloper of the Plum Hall
- Validation Suite for C++ (both with Thomas Plum). You can reach him at 393
- Leander Dr., Springfield OH, 45504-4906, by phone at (513)324-3601, or
- electronically at dsaks@wittenberg.edu.
-
-
- As P.J. Plauger recently reported in his "Editor's Forum," CUJ, September
- 1994, the draft C++ standard has reached a major milestone on its way to
- becoming a bona fide standard. This past July, the joint ANSI and ISO C++
- standards committees voted to submit the latest draft for registration as a
- committee draft (CD). CD registration is the first of three ballots that a
- draft must pass before it becomes an international standard (IS).
- As Plauger explained, the significance of this stage (aside from simply
- showing progress) is that it's supposed to be the point at which all the key
- features of the language are in place. As such, this is probably a good time
- to look back and see how the language has changed over the past few years.
-
-
- Who's Standardizing What?
-
-
- It's hard to talk about programming language standards without bandying about
- acronyms like ANSI and ISO, or arcane codenames like X3J16 or WG21. Although
- you clearly don't need to understand these terms to program competently in
- C++, I expect that many of you are at least a little interested in
- understanding the forces that shape your destiny.
- Table 1 contains a handy quick reference guide to standards organizations and
- committees affecting C and C++. You may find it helptul to refer to the table
- from time to time.
- ANSI (pronounced "AN-see") stands for the American National Standards
- Institute. ANSI is a trade association that sets standards for industrial
- products and processes. ANSI standards cover a wide assortment of products
- such as bar codes, bicycle helmets, heating and air conditioning equipment,
- and plywood. ANSI is not a US government agency and its standards are not in
- and of themselves binding by law. However, a government agency may adopt an
- ANSI standard for regulatory or procurement purposes, thus entwining that
- standard with laws.
- ANSI doesn't actually write standards; it establishes procedures for writing
- and approving standards, and then delegates most of the work to
- industry-specific standards committees. X3 is the ANSI-accredited committee
- that administers standards development for information processing systems
- (which includes programming languages). X3 operates out of the headquarters of
- CBEMA, the Computer and Business Equipment Manufacturer's Association. CBEMA
- (pronounced "see-BEE-ma") provides funding and staffing for X3's activities.
- X3 in turn establishes technical committees to develop specific standards. X3
- has chartered, among others, X3J11 and X3J16 to develop the ANSI standards for
- C and C++, respectively. X3J16 started working in early 1990, and has been
- meeting three times a year ever since.
- ANSI has counterparts in other nations, such as AFNOR (France), BSI (UK), DIN
- (Germany), JTSC (Japan), SCC (Canada), and many others. (To the rest of the
- world, please pardon me for not listing your national standards body.) These
- national standards bodies are members of ISO ("I-so"), the International
- Organization for Standardization. As with its members, ISO doesn't write
- standards, but rather charters committees to establish standards according to
- ISO procedures. ISO standards are not laws, but they have become the basis for
- international treaties, and so should not be taken lightly.
- ISO works cooperatively with yet another standards group, IEC, the
- International Electrotechnical Commission. ISO and IEC formed a joint
- technical committee, JTC1, to standardize information technology. JTC1's
- subcommittee SC22 oversees the development of international programming
- language standards.
- The international counterpart of an ANSI technical subcommittee like X3J16 is
- called a "working group." WG14 and WG21 are the SC22 working groups
- responsible for the international C and c++ standards, respectively. (WG21's
- fully-qualified name is ISO/IEC JTC1/SC22/WG21.)
- WG21 has been meeting jointly with X3J16 since the summer of 1991. The joint
- committees are working to produce a single document that, if all goes well,
- will become both the ANSI and ISO standards for C++. I often refer to the
- joint committees as a singular entity (the "joint committee" with no "s") or
- as WG21+X3J16.
-
-
- Base Documents
-
-
- Rather than write the C++ standard from scratch, X3J16 started by adopting the
- AT&T C++ Product Reference Manual (the PRM) version 2.1 as the base document,
- that is, as the initial working draft. C++ programmers are not as familiar
- with the PRM as they are with the Annotated C++ Reference Manual (the ARM)[1].
- The ARM is a textbook that includes a version of the PRM along with
- annotations and commentary that elaborate the language description, explain
- many language design decisions, and suggest implementation techniques. The ARM
- also includes chapters on templates and exceptions, which the PRM does not.
- Thus, strictly speaking, the ARM is not the base document. However, X3J16's
- first decisions eliminated most of the differences between the draft and the
- ARM (aside from the annotations and commentary). Therefore, for all practical
- purposes, the initial draft standard was based on the ARM, and so it's fair to
- call the ARM the base document.
- X3J16 selected the C standard as its second base document. Over the years, the
- committee has mined the C standard for passages describing parts of C that are
- supposed to be the same, or nearly the same, in C++. For example, the ARM does
- not include a grammar for the lexical tokens of C++ (constructs such as
- identifiers and literals). The C++ draft standard now incorporates the lexical
- grammar from the C standard. Similarly, the C++ draft's description of the
- preprocessor is now nearly identical to the one in the C standard.
- Nonetheless, the overall structure of the C++ draft still largely resembles
- that of the ARM.
- Occasionally, I receive requests for information about or copies of the "C++
- 3.0 standard" or something similar. There is no such thing. I think such
- questions arise because before there was a draft standard, compiler vendors
- used to claim compatibility with numbered versions of AT&T's C++ compiler.
- C++, like C, began at AT&T Bell Labs. AT&T started distributing a C++ compiler
- called cfront in the mid-1980s. For a few years, it was the only C++ compiler
- around. For several more years, cfront remained the de facto standard for C++
- compilers. Vendors of the first non-AT&T C++ compilers typically described the
- features their compilers supported by comparison with AT&T's product. This
- practice continued through the release of cfront 3.0 in 1991, even after X3J16
- had been at work for a couple of years. Comparisons with cfront 3.0 are now
- passé, but the confusion apparently still lingers.
- In short, there is not yet a C++ standard, but there is a draft C++ standard
- inching its way toward formal acceptance several years from now. That draft is
- based on, and still bears a strong resemblance to, the ARM, but it has also
- gone well beyond the ARM in many ways. What follows is a summary of the
- changes in the language wrought by standardization. But first...
-
-
- What About Libraries?
-
-
- Except for mentioning a few header files and functions, the ARM does not
- describe any run-time library accompanying a C++ implementation. From the
- beginning, X3J16 agreed that any acceptable C++ language standard must include
- a library. The current C++ draft includes a fairly extensive library,
- including language support functions, iostreams, complex arithmetic, and an
- adaption of the Standard C library. The draft also contains a variety of data
- structures, such as strings, dynamic arrays, and assorted container classes,
- most of which are provided as templates.
- I shall focus my attentions on the language itself, and defer to my colleagues
- at CUJ in describing the C++ library. (See Chuck Allison's column, "Code
- Capsules: The Standard C++ Library," CUJ, December 1994.) You can find even
- greater detail on the library in P.J. Plauger's CUJ columns over the last year
- and well into the future, or in his most recent book [2].
-
-
- Language Changes
-
-
- The joint committee has made a lot of changes in the C++ language definition.
- Many of the changes are substantive; they change the syntactic structure
- and/or the semantic interpretation of the C++ language itself. Other changes
- are just editorial -- they are changes in the description of the C++ language,
- not in the C++ language itself. The following lists omit changes that I
- believe are purely editorial.
- To give you a better sense of the nature of the substantive changes, I've
- grouped them into four broad categories:
- major extensions that dramatically increase the complexity of the language,
- and support alternative programming styles and paradigms
- minor enhancements that extend C++ in less dramatic ways
-
- changes that alter the meaning of existing features
- clarifications of existing features
- This is not a hard-and-fast classification scheme, and my judgments about what
- goes where are admittedly subjective. But hey, it's my column.
-
-
- Major Extensions
-
-
- The major extensions are:
- Templates
- Exception handling
- Run-time type information (including dynamic_cast)
- Namespaces
- Templates are a translation-time facility for writing generic functions and
- classes in terms of unspecified types. For example,
- template <class T>
- void swap(T &a, T &b)
- {
- T t = a;
- a = b;
- b = t;
- }
- defines a template for a function that will swap (exchange the values stored
- in) two objects of arbitrary type T. I described function templates in some
- detail in "Recent Extensions to C++," CUJ, June 1993. I devoted my entire
- column last month to template classes ("Designing Generic Container Classes,
- Part 6: Templates," CUJ, December 1994). You will find a lengthy discussion
- and numerous examples of templates in [3] and [4]
- Exception handling is a facility for orderly recovery from exceptional
- (typically erroneous) events during program execution. C++ exception handlers
- can only handle synchronous events -- the kinds of events that can be
- expressed as conditional expressions (for use in if or while statements or in
- assert macro calls). Exception handlers cannot intercept asynchronous events,
- such as device interrupts or hardware faults, which are better handled as
- signals.
- C++ provides exception handling through additional flow structures and library
- functions. A try-block is a compound statement (a sequence of statements
- enclosed in brackets) followed by a sequence of one or more handlers, also
- known as catch clauses. For example,
- try
- {
- // do something
- }
- catch (const char *s)
- {
- // catch an error
- }
- is a try-block. The handlers "catch" exceptions "thrown" by throw expressions
- executed in the compound statement or in functions called from within the
- compound statement, For example, executing
- throw "something happened";
- throws an exception that will be caught by the catch clause above. The throw
- terminates every active function invoked from the try-block and transfers
- control to the catch clause. Presumably, the catch clause handles the
- exception somehow.
- For much more detail on exceptions see Chuck Allison's "Code Capsules: C++
- Exceptions," CUJ, July 1994, as well as [3] and [4].
- Run-time type information (RTTI) is a facility for querying the dynamic type
- of a polymorphic object. A polymorphic object is an object whose class has at
- least one virtual function. An expression that refers to a polymorphic object
- has both a static type and a dynamic type, which may be different. For
- example, given:
- class B
- {
- public:
- virtual void f();
- ...
- };
- class D: public B
- {
- ...
- };
- B*pb = new D;
- then *pb is a polymorphic object with static (declared) type B but dynamic
- (run-time) type D.
- Using RTTI, you can query an object with a given static type to determine if
- it has a particular dynamic type. One form of query is an expression such as
- if (typeid(*pb) == typeid(D))
- ...
- You can also try to convert pb to a D * using
- D *pd = dynamic_cast<D *>(pb);
- which sets pd to 0 (a null pointer) if pb does not point to an object whose
- dynamic type is either D or a type derived from D.
- For a little more detail on RTTI see Chuck Allison's "Code Capsules:
- Conversions and Casts," CUJ, September 1994. For much more detail, see [4].
- Namespaces offer a mechanism for adding qualifiers to global names in the hope
- of reducing global name conflicts. Global name conflicts typically occur when
- a program attempts to use two different libraries that use the same global
- name but for different purposes. For example, libA.h might declare
- class status { ... };
- but libB.h might declare
-
- enum status { ... };
- A translation unit that tries to include both libA.h and libB.h will run up
- against compilation errors.
- Wrapping these global names in separate namespaces eliminates the conflicts.
- In libA.h, you write:
- namespace A
- {
- class status
- { ... };
- ...
- };
- and in libB.h you write:
- namespace B
- {
- enum status { ... };
- ...
- };
- A program that uses both libraries must refer to a status type by its
- explicitly-qualified name, i.e., as either A::status or B::status. If you
- would like the unqualified name status to mean A::status by default, you can
- write
- using namespace A;
- Then you must still refer to B::status by its fully-qualified name. Writing
- both
- using namespace A;
- using namespace B;
- reintroduces the name conflict.
- Chuck Allison's "Code Capsules: Visibility in C++," CUJ, May 1994 has more
- discussion of namespaces. [4] has even more.
-
-
- Minor Enhancements
-
-
- In addition to the major extensions, the draft C++ standard includes numerous
- minor enhancements:
- New keywords and digraphs as alternate ISO646-compliant spellings for their
- corresponding tokens
- Operator overloading on enumerations
- operator new[] and operator delete[]
- Relaxed restrictions on the return type of virtual functions
- wchar_t as a keyword representing a distinct type
- A Boolean type, bool
- Declarations in conditional expressions
- New cast notation
- Qualified names in elaborated-type specifiers
- Expressions of the form a.::B::c and p->::B::c (that is, with :: after the .
- or ->)
- Conversion from T **to const T *const *
- Layout rules for POD-struct and POD-union (POD-struct means "plain old data
- structure")
- Mutable class members
- Compile-time member constants
- Default return value (of 0) from main
- Empty initializer-clauses
- I covered the first five of these minor enhancements in "Recent Extensions to
- C++," CUJ, June 1993. [4] covers some the others. I'll get around to covering
- all of them in an upcoming column.
- All of the extensions and enhancements have added a lot of keywords to C++.
- See Table 2 for the complete list of C++ keywords, as listed in the current
- draft standard.
-
-
- Changes in Meaning
-
-
- The C++ draft also changes the behavior of numerous constructs from their
- previous behaviors specified by the ARM. Some changes simply prohibit features
- that were never intended to be, yet somehow slipped into some implementations.
- Other changes actually just change some constructs with explicitly-defined
- behavior to have different behavior. Roll with it.
- Here are the changes:
- Conditional statements introduce a new block scope.
- Enumerations are no longer integral types.
- The rules for class scopes have greater consistency.
- Friend function definitions in local classes are prohibited.
- Conversions between pointer to object type and pointer to function are
- prohibited.
- Operator op is not a valid identifier name except when used as a function
- name.
-
- cv-qualifiers (const and volatile) are ignored when modifying parameter types
- in function types.
- Parameter types that include pointer to array of unknown bound of T are
- prohibited.
- The lifetime of a compiler-generated temporary object lasts to the end of the
- full-expression in which the temporary is created.
- An enumerator may be accessed with a. or -) operators.
- Omitting the type specifiers (and implying int) is (1) prohibited in
- declarations wherever Standard C prohibits it, as well as (2) prohibited in a
- typedef without a type-specifier (e.g. typedef I;), and (3) deprecated in all
- other contexts.
- The scope of a declaration in a for-init-statement is restricted to the
- for-statement.
- The left hand side of a . or -> operator is always evaluated.
- A derived class constructor may explicitly initialize an inherited virtual
- base.
- T() has a specified value for every type T.
- The point of declaration for an enumerator is immediately after its
- enumerator-definition.
-
-
- Clarifications
-
-
- The ARM left a lot unsaid. The committee has been trying to fill in the
- details. Here's the summary of most of the issues clarified so far:
- The name of an untagged class named in a typedef is the class name for linkage
- purposes only.
- A function can be declared pure virtual in a derived class even if it's
- already defined in a base class.
- A destructor can be declared pure virtual, but it must also be defined.
- new T[0] returns a unique pointer, thus implying that the allocated array may
- consume memory.
- A pointer to a T member of a class cannot point to a T & member.
- Member bitfields cannot be declared static.
- A class may declare a static data member with an incomplete type.
- Lookup for names in an out-of-line member function looks in base classes
- before looking in enclosing classes.
- An aggregate initializer can only initialize an aggregate's non-static data
- members.
- Constructors and destructors for volatile objects may be compiled with or
- without volatile semantics.
- A C++ translator must look up the T in x. T::m (or p->T::m) as a type in two
- contexts -- (1) in the class scope of x (or *p), and (2) in the context of x
- -- but find T in only one.
- For the purpose of type lookup, the class name T is also considered a nested
- class member of class T.
- cv-qualifiers are not removed from function return types.
- cv-qualifiers that appear in the type-modifier for an array of T are applied
- to the type T and not to the array type itself.
- Pointers to members with reference or (possibly cv-qualified) void type are
- prohibited.
- Redundant cv-qualifiers within a single declaration are prohibited.
- References to (possibly cv-qualified) void are prohibited
- Function parameters that include references to incomplete array types are
- prohibited.
- Overload resolution does not consider functions as candidates if they cannot
- be called due to invalid reference initialization.
- cv-qualifiers are significant in overload resolution.
- Conversion function names may employ multiple pointers.
- Expression evaluation only considers overloaded operators when at least one
- operand has user defined type.
- cv-qualified reference types (e.g., T &const n) are prohibited.
- Static objects defined at file scope may be initialized at translation time
- (non-dynamically).
- As you can see, we at CUJ have plenty to write about for years to come.
-
-
- Meeting Dates, Etc.
-
-
- WG21+X3J16 will meet three times in 1995:
- March 5-10 in Austin, TX USA, hosted by Motorola
- July 9-14 in the San Francisco Bay area, CA USA, hosted by Sun Microsystems
- November 5-10 in Tokyo, Japan, hosted by ITSCJ
- If you would like to participate in the standards process as a member of
- X3J16, contact the vice-chair:
- Jose'e Lajoie
- IBM Canada Laboratory
- 844 Don Mills Rd.
- North York, Ontario M3C
- 1V7 Canada
- (416)448-2734
- josee@vnet.ibm.com
- References
-
- [1] Margaret A. Ellis and Bjarne Stroustrup. The Annotated C++ Reference
- Manual (Addison-Wesley, 1990).
- [2] P.J. Plauger. The Draft Standard C++ Library (Prentice-Hall, 1995).
- [3] Bjarne Stroustrup. The C++ Programming Language, 2nd. ed. (Addison-Wesley,
- 1991).
- [4] Bjarne Stroustrup. The Design and Evolution of C++ (Addison-Wesley, 1994).
- Table 1 Standard Organizations Affecting C and C++
- US Standards
- ----------------------------------------------------------------
- ANSI the American National Standards Institute
- X3 the ANSI committee for Information Processing
- Systems
- X3J11 the X3 technical committee standardizing C
- X3J16 the X3 technical committee standardizing C++
-
- International Standards
- ----------------------------------------------------------------
- ISO The International Organization for Standardization, an
- organization of national standards bodies such as
- ANSI (USA), AFNOR (France), BSI (UK), DIN
- (Germany), JTSC (Japan), SCC (Canada) and many
- others
- IEC the International Electrotechnical Commission
- JTC1 the Joint Technical Committee (of ISO and IEC) on
- Information Technology
-
- SC22 the JTC1 Sub-Committee for programming languages
- WG14 the SC22 Working Group standardizing C
- WG21 the SC22 Working Group standardizing C++
- Table 2 C/C++ Standards Quick Reference Guide
- and (+) false (+) signed
- and_eq (+) float sizeof
- asm for static
- auto friend static_cast (+)
- bitand (+) goto struct
- bitor (+) if switch
- bool (+) inline template
- break int this
- case long throw (+)
- catch (+) mutable (+) true (+)
- char namespace (+) try (+)
- class new typedef
- compl (+) not (+) typeid (+)
- const not_eq (+) typename (+)
- const_cast (+) operator union
- continue or (+) unsigned
- default or_eq (+) using (+)
- delete private virtual
- do protected void
- double public volatile
- dynamic_cast (+) register wchar_t (+)
- else reinterpret_cast (+) while
- enum return xor (+)
- explicit (+) short xor_eq (+)
- extern
- (+) indicates a new keyword (not found in the ARM)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Questions & Answers
-
-
- Are Marching Pointers Really Faster?
-
-
-
-
- Kenneth Pugh
-
-
- Kenneth Pugh, a principal in Pugh-Killeen Associates, teaches C and C++
- language courses for corporations. He is the author of All On C, C for COBOL
- Programmers, and UNIX for MS-DOS Users, and was a member of the ANSI C
- committee. He also does custom C/C++ programming and provides
- SystemArchitectonicssm services. His address is 4201 University Dr., Suite
- 102, Durham, NC 27707. You may fax questions for Ken to (919) 489-5239, Ken
- also receives email at kpugh@allen.com (Internet) and on Compuserve
- 70125,1142.
-
-
- Q
- I always enjoy your column because it is always relevant. My work is
- scientific computing. In your piece "Increasing Execution Speed" you contrast
- zeroing an array using an explicit index with a method that uses pointer
- incrementing. You comment that the pointer increment is "usually faster." Oh?
- My work chart looks like Table 1.
- I coded up a comparison of a million zeros each way (Listing 1). On my 486
- with Turbo C they come out nearly the same: 1.2 seconds for the index versus
- 1.0 seconds for the pointer. My friend Walter Herrick tested the programs on a
- couple of other platforms and got nearly identical results (Listing 2), Why so
- nearly equal? Microprocessors do integer multiplications in hardware, which is
- fast, compared with shipping bytes to memory.
- There are two good reasons to avoid addressing an array by a moving pointer,
- using an index instead. First, the pointer method does not generalize to
- arrays of more than one dimension. Second, the marching pointer is not
- language-portable. In scientific programming, one frequently has to move
- algorithms between C, FORTRAN, and Pascal. Only C allows moving pointers.
- Other languages, especially those with built-in array index validation, do not
- permit pointers to move.
- It is true that the K&R book has many examples of marching pointers. But that
- does not make marching pointers a good idea! K&R was written when multiplies
- were expensive and adds were cheap. We are beyond that now, and can afford a
- different point of view, in which clarity is regarded as being more important
- than minimizing source code length. For one view of how to restrict ANSI C as
- to promote clean coding (no side effects, one consequence per line of code,
- transportable constructs, etc.) see the article by Helene Ballay and Rainer
- Store, "A Tool for Checking C Coding Conventions," CUJ, July, 1994, p. 41.
- From the sidebar I judge that PJP disagrees with those authors but in my
- experience (moving code), I support their view.
- Mike Lampton
- Berkeley, CA
- A
- Thanks for sharing your test results with us. It's true that in many cases,
- with today's increased microprocessor speeds, the relative efficiency of a
- particular approach is unimportant. This is not necessarily the case for
- specialized applications, such as in embedded systems, which may use less
- powerful chips. The use of instruction caches and memory caches complicates
- the issue by making it difficult to calculate which approach will always be
- most efficient.
- With GUI interfaces becoming much more common these days, much of the CPU time
- is spent in interacting with the user. The application programmer usually does
- not have much opportunity to make the GUI efficient (other than to insure that
- any repainting of the screen is minimal -- i.e. the entire virtual screen is
- not redrawn). I estimate that about 80% of the CPU time in a typical
- application is spent in GUI libraries and code. The reminder is in "real"
- processing. If you were able to double the efficiency of the real processing,
- you would only cut 10% from the overall CPU time.
-
-
- Memory overwrites
-
-
- Q
- I'm a longtime subscriber to The C Users Journal and I've written a program
- using Turbo C v3.0 that writes to memory that doesn't belong to it,
- specifically memory that MS-DOS uses. After running the program, in most cases
- memory is so corrupted that DOS doesn't recognize the keyboard, COMMAND.COM
- fails to reload, or, worse yet, DOS's information on the hard disk format has
- been overwritten and thereafter any write to the disk destroys data there.
- I've done the usual in trying to debug the program, including using MemCheck
- v3.0 and the latest PC-Lint. I have not figured out how to zero in on the code
- that is responsible using either of these two commercial tools.
- I suspect some third-party commercial graphics libraries that I am using are
- doing the actual writing to DOS memory, but haven't been able to pinpoint the
- problem.
- The next step I want to try is to generate checksums for various blocks of
- memory that DOS uses and then throughout my program periodically recalculate
- the checksums in an effort to move in closer to the bad code. The problem
- here, however, is that I've been unable to locate any detailed maps of memory
- that DOS uses. No one claims to know where I can obtain them.
- Do you know of any source for DOS memory maps or do you have any suggestions
- on how I can find the cause of my problem?
- Dennis C. Fait
- Butler, PA
- A
- Memory problems are the bane of the C programmer. Give a programmer an address
- and there is no telling what he or she may do with it. PC-Lint can indicate
- potential problems in source code for many types of pointer errors. However it
- cannot analyze dynamic errors. For example, it is easy (too easy some might
- say) to increment an address beyond its proper bounds by looping too many
- times. Debug output placed in the source code can pinpoint these types of
- errors. Since it sounds like you do not have access to the source code for the
- third party libraries, you will have to try a more indirect approach.
- Some of your problems may be caused by errors other than memory overwrites.
- For example, I know that several DOS versions back you could cause disk
- corruption by re-opening the same file for writing without closing it. (I have
- not tried this recently, as my disks have grown too large for easy repair in
- case it remains a problem with MS-DOS.)
- A good book on MS-DOS programming will typically include some sort of MS-DOS
- memory map, though maybe not to the level of detail you are looking for. You
- might check out Dos Internals, by Geoff Chappell [1]. This book dedicates a
- couple of chapters to MS-DOS memory management; it may shed some light on your
- problem. Alternatively, run the MS-DOS MEM command with the /D option. This
- will give you a fairly detailed snapshot of your memory configuration. Most
- likely you will discover that MS-DOS is not confined to one contiguous region,
- which can make this particular debugging approach difficult.
- Fortunately, just as there are many ways to create memory overwrite errors,
- there are many possible ways to track down those errors. For example, you can
- temporarily bypass calls to the suspected routines. You ought to use a dynamic
- test to do this, rather than using conditional compilation. Use something like
- this:
- if (full_program)
- potential_offending_function();
- as opposed to this:
- #ifdef FULL_PROGRAM
- potential_offending_function();
- #endif
- Using a dynamic test will keep the memory layout of your executable close to
- that for which you are having problems.
- You could also try using different versions and configurations of MS-DOS and
- see if the results are any different. The memory layout would be different and
- so you should get different results. If no differences occur, the error may be
- in overwritting buffers internal to your program rather than in MS-DOS.
- Another approach would be to compile it in protected mode and link it with a
- protected mode linker. Your graphic libraries may not necessarily work in that
- mode, although many of them do. Memory access errors in protected mode will
- cause a fault, rather than crash MS-DOS.
- The above is not an exhaustive list. If nothing else works, you can, of
- course, try another graphics library. I admit that's not a very desirable
- option. Hopefully its interface will be similar to the one you are currently
- using, so you won't have to re-write much code.
- Reference
- [1] Geoff Chappell. Dos Internals (Addison-Wesley, 1994), pp. 131-192.
- Table 1 Cost of zeroing an array: indexing vs. pointer operatons
- Index Pointer Cost
- ------------------------------------------------------
-
- increment index decrement count equal
- get address get address add 8*i versus add 8
- place the zero place the zero equal
-
- Listing 1 A program to compare indexing vs. pointer addressing for speed
- /* aspeed.c array vs pointer speed test */
- #include <stdio.h>
- #include <time.h> /* gettime() */
-
- #define SIZE 1000
- #define REPS 1000
-
- void zerobyincrement(double array[], int howmany);
- void zerobydecrement(double array[], int howmany);
- void zerobypointer(double array[], int howmany);
- double fetchtime( void);
-
- void main()
- {
- double darray[SIZE];
- int i;
- double t0, t1;
- printf("incrementing index...\n");
- t0 = fetchtime();
- for (i=0; i<REPS; i++)
- zerobyincrement(darray, SIZE);
- t1 = fetchtime();
- printf("time... %9.21f \n", (t1-t0) / CLOCKS_PER_SEC);
- printf("decrementing index...\n");
- t0 = fetchtime();
- for (i=0; i<REPS; t++)
- zerobydecrement(darray, SIZE);
- t1 = fetchtime();
- printf("time... %9.21f \n", (t1-t0) / CLOCKS_PER_SEC);
- printf("marching pointer... \n");
- t0 = fetchtime();
- for (i=0; i<REPS; i++)
- zerobypointer(darray, SIZE);
- t1 = fetchtime();
- printf("time... %9.21f \n", (t1-t0) / CLOCKS_PER_SEC);
- }
-
- void zerobyincrement(double array[], int howmany)
- {
- int i;
- for (i=0; i<howmany; i++)
- array[i] = 0.0;
- }
-
- void zerobydecrement(double array[], int howmany)
- {
- int i;
- for (i=howmany-1; i>=0; i--)
- array[i] = 0.0;
- }
-
- void zerobypointer(double array[], int howmany)
- {
- while (howmany--)
-
- *array++ = 0.0;
- }
-
- double fetchtime( void )
- {
- return (double) clock();
- }
- /* End of File */
-
-
- Listing 2 Results of running program aspeed.c (Listing 1)
- RESULTS:
-
- increment decrement pointer
- 286 6MHz, 1 million zeros: 50.53us 51.36us 54.81us
- 486 50MHz, l0 million zeros: 1.21us 1.16us 1.08us
-
- 486-50 using Microsoft C 6.0 increment decrement pointer
- 1.04 1.04 0.93
- Sun SPARCserver 1000 using Sun C compiler fully optimized
- 0.06 0.08 0.06
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- CUG New Releases
-
-
- MICRO-C, MIMEQP, BSPline, and More
-
-
-
-
- Victor R. Volkman
-
-
- Victor R. Volkman received a BS in Computer Science from Michigan
- Technological University. He has been a frequent contributor to C/C++ Users
- Journal since 1987. He is currently employed as Senior Analyst at H.C.I.A. of
- Ann Arbor, Michigan. He can be reached by dial-in at the HAL 9000 BBS (313)
- 663-4173 or by Usenet mail to sysop@hal9k.com.
-
-
-
-
- New Acquisitions
-
-
- MICRO-C (CUG #422): MICRO-C C compiler for the PC, over 70 example programs
- for use with MICRO-C, and demonstration compiler for embedded systems with
- simulators.
- RECIO, MIMEQP, ACCTPOST, RDCF, and BSPLINE (CUG #423): a bevy of tools from
- authors from around the U.S., including:
- Stream-style record I/O
- MIME binary encode/decode for email
- General ledger posting w/32-bit arithmetic library for MS-DOS
- Re-entrant DOS-Compatible File System for embedded systems
- Classic BSPLINE rendering algorithm
-
-
- CUG #422: MICRO-C C Compiler
-
-
- Dave Dunfield (Nepean, Ontario, Canada) submits an entire suite of tools from
- the MICRO-C C compiler development system. This submission includes the
- MICRO-C C compiler itself (for MS-DOS), more than 70 useful sample programs
- with full C source, and a demonstration version of MICRO-C for embedded
- systems. MICRO-C is a tiny compiler which can run in less than 32Kb of RAM,
- yet is highly independent of CPU and OS. Other distributions of this compiler
- will run on 68HC08, 6809, 68HC11, 68HC16, 8051/52, 8080/8085, and 8096 CPUs.
- The CUG Library distribution includes a fully functional MICRO-C compiler
- executable, built for the MS-DOS 80x86 environment. This version generates
- code in .ASM format, so Microsoft MASM, Borland TASM, or equivalent are
- required (not included). MICRO-C version 3.02 (as released on 03/22/94) is
- immediately available as CUG#422 in a set of four diskettes.
- MICRO-C provides much more functionality than Small-C and its many
- derivatives. Specifically, MICRO-C supports all C statements, operators, and
- preprocessor directives as well as inline assembly code. MICRO-C includes data
- types for int, char, unsigned, struct, union, pointers, and typecasting. In
- other words, MICRO-C gives you everything possible except for typedef, long,
- double, float, enum, and bit fields. The runtime library does include a long
- arithmetic package with arbitrary precision up to 256 bits.
- Even if you're not especially interested in the MICRO-C compiler, the you may
- wish to take advantage of the collection of more than 70 sample programs.
- Although there are simply too many to catalog here, I've listed two dozen of
- the most interesting:
- CCREF -- C source cross referencing program
- COMEXT -- Extract comments from C sources
- OBSCURE -- Make C program unreadable (but it still
- compiles)
- PCC -- Pretty Printer for C (source formatter
- CALC -- A TSR programmers (HEX/DECIMAL
- calculator
- CMOS -- Read/Write/Verify CMOS RAM from/to/with
- disk file
- CSET -- TSR map of IBM PC character set
- DIFF -- Displays differences between text files
- GREP -- Like UNIX "GREP" search utility
- HEM -- Hardware Exception Monitor TSR to trap
- unexpected interrupts
- LZC -- Laser commander TSR to control
- HP-compatible printers
- MEMSAVE -- Saves memory image to file
- MTERM -- Tiny (10K!) TSR ANSI terminal with
- XMODEM
- SHOWEXE -- Displays information about an .EXE file
- TFB -- TSR File Browser
- VALIDATE -- PD version of McAfee's validate. Verify file
- with two CRCs
-
- LAPTALK -- A terminal program with script interpreter
- XMODEM -- External file transfer program
- MICROCAD -- Mouse-based drawing program
- FE -- Font Editor
- ASM86 -- 8086 assembler
- BASIC -- A simple BASIC interpreter
- DIS85 -- 8085 Cross Disassembler
- TTT3D -- 3 dimensional tic-tac-toe
- All files in the MICRO-C archives are for personal use only. Any commercial
- use of information or programs contained in these archives requires written
- permission from Dunfield Development Systems. You can obtain source code for
- the MICRO-C compiler by purchasing a license for $100 from Dunfield
- Development Systems.
-
-
- CUG #423: RECIO, MIMEQP, ACCTPOST, RDCF, and BSPLINE
-
-
- The CUG Library has always accommodated C/C++ archives both big and small.
- This month, I've compiled an anthology of five small but outstanding source
- archives. William Pierpoint (Camarillo, CA) submits his comprehensive library
- for streamstyle record I/O. Karl Hahn (Sarasota, FL) contributes MIME binary
- encode/decode routines for use with e-mail tools. Philip Erdelsky (San Diego,
- CA) releases source for general ledger posting with 32-bit math library, and a
- reentrant, DOS-Compatible File System for embedded systems. Last, Keith
- Vertanen (Pine Springs, MN) sends his brief but succinct implementation of the
- BSPLINE rendering algorithm. Again, all five archives are immediately
- available on a single diskette as CUG volume #423.
-
-
- CUG #423A: RECIO -- Record Input Made Easy
-
-
- The RECIO library contains more than 50 functions and macros enabling file
- input in which each line becomes a data record, with each record subdivided
- into fields. Fields may be either character delimited or column delimited.
- RECIO's learning curve is not steep, since many functions are based on
- analogous counterparts in stdio. RECIO is freeware and is protected by the GNU
- Public License. Version 2.00 (as released 04/16/94) appears along with several
- unrelated archives on CUG volume #423.
- Since virtually every program has to do input or output, C programmers are
- very familiar with the stdio library. Many functions in the recio library are
- analogous to those in the stdio library, as shown by the following table:
- Analogous stdio/recio components
- stdio recio
- ----------------------------
- FILE REC
- FOPEN_MAX ROPEN_MAX
- stdin recin
- fopen ropen
- fclose rclose
- fgets rgetrec
- fscanf rgeti, rgetd, rgets, ...
- clearerr rclearerr
- feof reof
- ferror rerror
- RECIO includes a makefile only for Borland Turbo C, although it should work
- with other platforms as well.
-
-
- CUG #423B: MIMEQP -- A Better Encode/Decode For E-mail
-
-
- MIMEQP inputs files that are mostly ASCII, but contain some non-ASCII
- characters, and encodes them so that the output file is all ASCII. The
- characters that were ASCII remain so, so the encoded file is human-readable.
- The encoding algorithm also limits line lengths to 72 characters. MIMEQP is
- useful for sending files containing non-ASCII characters through mail servers.
- MIMEQP (or MIME Quoted-Printable) is defined in RFC 1341. MIMEQP (as released
- on 05/22/93) appears along with several unrelated archives on CUG volume #423.
- MIME is an acronym for Multipurpose Internet Mail Extensions. It builds on the
- older standard by defining additional fields for mail message headers. These
- headers describe new types of content and organization for messages.
- MIME allows mail messages to contain the following:
- Multiple objects in a single message
- Text of unlimited line length or overall length
- Character sets other than ASCII
- Multi-font messages
- Binary or application-specific files
- Images; Audio, Video, and multi-media messages
- MIMEQP encoding replaces all control characters except '\n' and '\t,' all
- occurrences of '=', and all characters whose ASCII code is greater than 127
- with =XX, where XX is the hex value of the ASCII code. The CUG library
- distribution includes a substantial overview of the MIME standard by Mark
- Grand, along with an MS-DOS executable.
-
-
- CUG #423C: ACCTPOST General Ledger Utility
-
-
- The Plain Vanilla Posting Program II is a simple program that takes over three
- tedious accounting tasks: posting from the general journal to the general
- ledger, drawing a trial balance, and putting account balances into a report.
- The Plain Vanilla Posting Program II is written for the compact memory model
- (near function, far data) in Turbo C 2.0 for MS-DOS. The author claims some
- source portability to UNIX variants as well. The Plain Vanilla Posting Program
- II (as released on 09/01/90) is included with unrelated source archives on CUG
- volume #423.
- The only non-portable part of the program appears in ARITHMET.C, which
- contains embedded assembly language code. This allows ACCTPOST to catch
- arithmetic overflows, which would be disastrous for an accounting application
- if undetected. Otherwise, ACCTPOST is just a simple 32-bit arithmetic package,
- with only five operations: addition, negation, multiplication, division by 10,
- and remainder computation for a 32-bit number divided by 10. The last two
- operations cannot produce overflows and could have written in standard C.
- Porting to a machine with native 32-bit arithmetic should be simple.
- Although the Plain Vanilla Posting Program II is copyrighted, it carries only
- one restriction: you may not sell the program or any program derived from it.
- You may give it away or charge a reasonable fee for media duplication, but may
- not charge anything for the software itself.
-
-
-
- CUG #423D: Re-entrant DOS-compatible File System
-
-
- RDCF is a reentrant and ROMable DOS-compatible file system, designed for use
- with floppy diskettes and hard disk partitions that do not exceed 32 MB in
- size. This distribution also includes a simple disk-caching package designed
- for use with RDCF, which may also be used separately. The distribution
- includes an MS-DOS utility called FILES, written primarily to test RDCF, in
- both source and executable form. FILES performs a number of operations on DOS
- files, including some that cannot be performed from the DOS command line. For
- example, FILES' DIR command shows the remains of deleted files. This
- distribution includes complete C source code and documentation for FILES,
- including a fairly detailed description of the DOS file system.
- RDCF is copyrighted, but it is freeware. You may copy it and pass it on
- freely, as long as you pass on the entire, unmodified package, with its
- copyright notices intact. You may not resell it, although you may charge a
- reasonable fee for diskette duplication, shipping, and handling.
-
-
- CUG #423E: Classic BSPLINE Rendering Algorithm
-
-
- A spline is a mathematical construct from numerical analysis which is used to
- fit a curve to an arbitrary set of points. It has obvious uses in statistics
- and computer graphics rendering. For a description of spline theory and
- algorithm implementation, see Elementary Numerical Analysis by Kendall
- Atkinson (1985, Wiley & Sons). BSPLINE performs polynomial interpolation and
- returns an array of coefficents to describe a spline. BSPLINE was written in
- C++ for the Borland compiler, though it uses few, if any, features of C++. The
- library uses function calls from the Borland Graphics Interface to render the
- bspline on EGA or VGA displays. BSPLINE (as released on 03/11/94) is included
- with other unrelated archives on CUG volume #423.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Editor's Forum
- I just got to know two communities better. I've been a member of both for lo
- these many years, but not the active participant I probably should have been.
- One community is the town I live in. The other is the global network of people
- I interact with professionally. Both suddenly loomed larger in my personal
- sphere.
- The town of Concord, Massachusetts still holds annual town meetings to conduct
- its business. This is genuine participatory democracy at its finest -- you
- literally must attend and stand up to be counted to vote on an issue. The bad
- news is that typically only a few percent of the town actually attends town
- meetings. I, for one, have in the past left this duty to my fellow townsfolk,
- who seem to be doing just fine at it, thank you.
- But a group of people decided to oppose a recent town vote to add six units of
- affordable housing to our rather wealthy backwater. Yes, I said six units.
- They raised the signatures needed to call a special town meeting to rescind
- the vote. I was pleased to see 1,400 of my fellow citizens turn out to soundly
- defeat the move, more than doubling the record for the largest town meeting in
- the past. It's enough to renew my faith in participatory democracy.
- Within hours of that event, I logged onto the internet with a Mosaic browser
- for the first time. Before I knew it, I had skimmed around the world several
- times, noting places and things undreamed of in my earlier philosophy. My
- hotlist is growing nightly as I chase strands of the World Wide Web. I've yet
- to tire of the adventure. E-mail is nothing like this.
- I can hardly wait for a dedicated high-speed link. Already, I've set aside a
- machine to serve as my "store front" on the Web, and I'm planning my home page
- with all the alacrity of a pre-schooler at the easel with finger-paints. It's
- enough to renew my enthusiasm for the overworked term "global village."
- In a world with decaying neighborhoods, cynical politics, and weakening
- industries, it's a pleasure to find the seeds of new communities and
- enterprises. They won't always look like the ones we left behind, or the ones
- we may dream of, but they are new and exciting for all that. It reminds me of
- a bit of hillbilly wisdom from my roots in West Virginia -- We ain't what we
- wanna be, and we ain't what we're gonna be, but we ain't what we wuz.
- Happy new year.
- P.J. Plauger
- pjp@plauger.com
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- New Products
-
-
- Industry-Related News & Announcements
-
-
-
-
- Pure Software Ships Purify 3 and PureCoverage 1.0
-
-
- Pure Software Inc. has begun shipping Purify 3.0 and PureCoverage 1.0. Purify
- 3.0 is a software quality development tool that detects run-time errors and
- memory leaks in C/C++ applications. Purify incorporates Pure Software OCI
- technology. OCI examines the object code and inserts checking instructions
- around the memory functions to monitor usage at run time and report illegal
- memory accesses and leaks. OCI lets Purify analyze applications including
- shared and third-party libraries.
- Features of Purify 3.0 include a GUI that provides message browsing while an
- application is running and an outline view which displays error messages and
- simplifies navigation. Also included in Purify 3.0 is single-click access to
- source code, which lets developers make changes at the point where an error
- occurred.
- Purify 3.0 is integrated with PureCoverage 1.0, Pure Software's code coverage
- analysis product. PureCoverage 1.0 lets users manage, manipulate, and analyze
- data into a variety of formats and reports. Data can be viewed through Purify
- 3.0's outline browser and then manipulated according to user specifications.
- Inserted at the object level, PureCoverage integrates into the development
- environment. PureCoverage supports C/C++ and FORTRAN, and runs on multiple
- platforms including Sun, Solaris, and HP.
- Purify 3.0 is priced at $1,298. PureCoverage 1.0 is priced at $898. For more
- information contact Pure Software Inc., 1309 S. Mary Ave., Sunnyvale, CA
- 94087, (408) 720-1600; FAX: (408) 720-9200; E-mail: info@pure.com.
-
-
- ARSoftware Releases ARC++ 2.0
-
-
- ARSoftware has released ARC++ v2.0, a C/C++ development tool. According to the
- company, with a few adjustments to the existing make or project files, ARC++
- automatically determines whether a header file change affects previously
- compiled modules and alters the make process accordingly, giving the user
- control over recompiles.
- Features of ARC++ v2.0 include exportable classes that eliminate the need for
- header files and intelligent macros that provide encapsulated code generation.
- ARC++'s macro capability automatically analyzes class definitions, which can
- loop, branch, and access the parse tree/symbol table to generate code based on
- previous declarations. Defined within C++'s block structure, these macros are
- capable of being inherited. Callback macros in multiple base classes ensure
- that derived calls have the appropriate support code generated. Other features
- of ARC++ include: overloaded enumerators, bound function pointers, prototyped
- Vararg functions, automatic functions, arrays with bounds, modify detection,
- user-defined operations, combination operators, user-defined modifiers, and
- hidden arguments.
- ARC++ v2.0 for DOS and Macintosh is priced at $249, and $299 for UNIX. Upgrade
- pricing for existing users is $29 for DOS and Macintosh, and $39 for UNIX. For
- more information contact ARSoftware, 8201 Corporate Dr., Suite 1110, Landover,
- MD 20785, (800) 257-0073 or (301) 459-3773; On-line catalog:
- URL:http://arsoftware.arclch.com.
-
-
- Cygnus Announces GUI for GNU Debugger
-
-
- Cygnus Support has announced plans to release a GUI for their GNU Debugger,
- GDB. The GUI for GDB provides programmers with a consistent interface for
- native UNIX development, as well as cross-platform development hosted on UNIX
- X11 and MS-Windows 3.1 platforms. The Cygnus GUI allows users to extend or
- modify the GUI based on their user-interface preferences.
- According to Cygnus, the design of the GDB interfaces emphasizes the
- information display and the direct manipulation of displayed objects.
- Additional features of the GUI include windows for source, object code, data
- display, stack frames, breakpoint/watchpoint lists, and a target state window.
- The GUI also includes context-sensitive, on-line help and keyboard shortcuts
- for common operations such as single-stepping. Both high-level and low-level
- graphics capabilities will also be available in the GUI. All features are
- available to both native and cross-debugging configurations of GDB. The GUI
- will be available on all host/target platforms currently supported by Cygnus.
- Source code is also available.
- The company plans to release the UNIX version of the GUI for GDB January 1,
- 1995, and the Windows version April 2, 1995. For more information contact
- Cygnus Support, 1937 Landings Dr., Mountain View, CA 94043, (415) 903-1400;
- FAX: (415) 903-0122.
-
-
- Apogee Announces Apogee-C/C++ and Apogee-FORTRAN 77/90 Compilers
-
-
- Apogee Software has announced Apogee-C/C++ and Apogee-FORTRAN 77/90 compilers
- for SPARC. Apogee-C/C++ and Apogee-FORTRAN 77/90 compile programs written in
- C/C++ and FORTRAN 77 or FORTRAN 90 into machine code optimized for RISC-based
- computers.
- Apogee-C/C++ is an optimizing compiler accepting C/C++. The C dialects
- accepted include the ISO/ANSI C standard and Kernighan & Richie C. The C++
- dialects accepted include the current draft of the ANSI C++ standard
- (including templates and exceptions), Cfront 2.1 C++, and AT&T 3.0 C++. To
- assist in converting older C or C++ programs to ANSI C or C++, the compiler
- also optionally accepts non-standard uses of C/C++ and provides warning
- messages.
- Apogee-FORTRAN 77/90 is an optimizing compiler accepting two dialects of
- FORTRAN: the 1977 and 1990 ISO/ANSI standard. In addition, the compiler
- accepts extensions found in Sun, IBM, VAX/VMS, Cray, and MIL-STD 1753 FORTRAN
- 77s. The FORTRAN 90 mode is compliant with the ISO/ANSI FORTRAN 90 standard.
- The Apogee-C/C++ and Apogee-FORTRAN 77/90 compilers work with several
- programming tools that are offered as optional items on the same CD-ROM: the
- TotalView graphical interface debugger from BBN, the SNiFF++ visual
- programming environment from takeFive Software (C/C++ only), and the Sentinel
- error detection programming tool from AIB Software. In addition, there is a
- free GDB debugger on the CD-ROM modified by Apogee to work with C/C++ as well
- as with FORTRAN.
- Apogee compilers have been specifically tuned to take advantage of the
- hardware features for each implementation of the SPARC architecture including:
- SuperSPARC, hyperSPARC, MicroSPARCI, MicroSPARC II, and PowerUp. Apogee
- compilers also let the user select the specific target processor by a compiler
- switch. For more information contact Apogee Software Inc., 1901 S. Bascom
- Ave., Suite 325, Campbell, CA 95008-2207, (408) 369-9001; FAX: (408) 369-9018;
- E-mail: info@apogee.com.
-
-
- IST And V.I. Corporation Announce X-Designer 4
-
-
- Imperial Software Technology and V.I. Corporation have announced X-Designer 4,
- a cross-platform GUI builder for Motif and Windows applications. Features of
- X-Designer 4 include a Windows-compliant mode for Windows development and the
- Microsoft Foundation Class Library as the Windows interface.
- In Windows mode, X-Designer can generate MFC code, which can then be compiled
- with native Windows tools, such as Visual C++. Also in Windows mode,
- X-Designer indicates which Motif resources do not have an equivalent in
- Windows and which existing X-designer Motif interface design files may be
- loaded in X-Designer 4. X-Designer's toolbar has a Compliance button which is
- marked with a red cross if the design contains non-Windows-compliant design
- elements. Pushing this button displays a list of the non-compliant elements,
- which may then be modified.
- Other features of X-Designer 4 include: internationalization, drag and drop,
- tear-off menus, the ability to generate C/C++ code, HyperText Help, geometry
- management capabilities, an unlimited Undo button, and an intuitive Compound
- String Editor. X-Designer 4 supports Sun, HP, Silicon Graphics, IBM, DEC, and
- SCO platforms, with deployment on Windows supported by MFC.
- X-Designer 4 is priced at $3,500 for the first license. Upgrades are free to
- existing X-Designer users with a current support contract. For more
- information contact Imperial Software Technology, Reading, United Kingdom, +44
- 734-587055 or V. I. Corporation, Northampton, MA, (413) 586-4111.
-
-
- Tartan Introduces Tartan C and Tartan C/C++ 2.0
-
-
- Tartan, Inc. has introduced Tartan C and Tartan C/C++ 2.0 compilers for the TI
- TMS320C3x and C4x digital signal processors (DSPs). Both the C and C++
- compilers use the on-chip capabilities of TI's C3x and C4x DSPs, and are
- switch-selectable for both of the processors.
- Tartan C and Tartan C/C++ are development systems which include:
- highly-optimized C (Tartan C) and C++ (Tartan C/C++) compilers, modular
- runtime systems with a selective linker, a C3x/C4x assembler, floating-point
- math libraries, object file utilities, and documentation. Both Tartan C and
- Tartan C/C++ support the SPOX real-time DSP operating system from Spectron
- Microsystems. SPOX provides a real-time, multitasking kernel and bridges to
- other operating systems such as Windows and UNIX. In addition, Tartan C and
- C/C++ both include the FasTar single-precision runtime math library, as well
- as tools for building and maintaining real-time DSP applications.
-
- Optional components for Tartan C and Tartan C/C++ include a source and
- machine-level symbolic debugger and an instruction simulator. Native,
- window-based debugger user interfaces are available for Microsoft Windows for
- the PC and Sun OpenWindows for the SPARC. The VecTar and SigTar DSP math
- libraries are also available for use with C and C/C++ compilers,
- Tartan C is available for PC and Sun SPARC platforms. Prices for the Tartan C
- compiler start at $1,495. Existing Tartan C/C++ customers with maintenance
- will be upgraded automatically to version 2.0. For more information contact
- Tartan Inc., 300 Oxford Dr., Monroeville, PA 15146, (412) 856-3600; FAX: (412)
- 856-3636.
-
-
- ViewSoft Releases UTAH 1.1
-
-
- ViewSoft, Inc. has released UTAH 1.1, a C/C++ application and GUI builder.
- UTAH 1.1 lets programmers create GUIs without adding interface dependencies to
- their program objects or writing interface code. Included in UTAH 1.1 are
- several GUI building technologies that automate the assembly, testing, and
- maintenance of GUIs: the Editable Object System (EOS), Semantic Interface
- Technology, and a User Extensible Toolkit.
- The Editable Object System (EOS), which is a C++ class library with
- platform-independent extensions, provides reflection (self-describing objects)
- and run-time-binding. User objects inherit this extended functionality from
- base EOS objects. The Semantic Interface Technology uses "smart components"
- that eliminate interface dependencies for the program objects. UTAH
- automatically performs type conversion and synchronization of interface
- objects with program data whenever either interface or program variables
- change. This automatic synchronization frees users from routine user-interface
- event management, while preserving event access for performance or custom
- control. UTAH's User Extensible Toolkit lets users create interactions which
- are then used, like the components supplied with UTAH, to build complex
- interactions without learning the interactor' s API or sub-classing to get
- control. Existing VBX controls can be imported into UTAH and used as if they
- were native UTAH components.
- UTAH 1.1 for Windows is priced at $1,490. For more information contact
- ViewSoft Inc., Provo, UT, (801) 377-0787.
-
-
- Continuus Software Ships Continuus 4.0
-
-
- Continuus Software Corporation has begun shipping Continuus 4.0, a
- configuration management tool. Continuus 4.0 lets software development teams
- coordinate and manage their activities, including change tracking, task
- management, code development, testing, documentation, release management, and
- ongoing maintenance.
- Continuus 4.0 includes a Distributed Code Management (DCM) facility which lets
- geographically dispersed development teams synchronize common project data,
- whether or not the sites are linked by a network. DCM also includes a Vendor
- Code Management utility which tracks software source or libraries received
- from external suppliers, and merge them with local modifications. Also
- included in Continuus 4.0 is a code migration facility that preserves a
- project's original file structure, and automatically loads SCCS and RCS data.
- Project migration can be performed both interactively and in batch mode.
- Continuus 4.0 also includes a Motif GUI as well as a command line interface.
- Other features of Continuus 4.0 include an "off-the-shelf" customizable
- process model, which defines user roles, and ObjectMake, an object-oriented
- Make facility. ObjectMake is compatible with common makefile formats, and
- products built with ObjectMake can be automatically version-controlled and
- shared by multiple users.
- Continuus 4.0 supports major UNIX platforms. Continuus/CM and Continuus/PT are
- available on a floating license basis. Continuus 4.0 is priced at $4,000 per
- simultaneous user. For more information contact Continuus Software
- Corporation, 108 Pacifica, Irvine, CA 92718, (714) 453-2200; FAX: (714)
- 453-2276.
-
-
- Tower Concepts Announces GlobalTrack and Ports Razor
-
-
- Tower Concepts has announced GlobalTrack, a distributed processing software
- module for Razor, the company's UNIX-based problem tracking and configuration
- management tool suite. Razor is an integrated tool suite for developers,
- combining a tailorable issue-tracking system with traditional version control
- and build coordination capabilities. Using the Internet or other
- X.400-compliant systems, GlobalTrack lets development teams in different
- geographic locations coordinate their activities, priorities, and objectives
- though synchronized issue-tracking databases using the existing e-mail
- networks for communication.
- In another notice, Tower Concepts announced a port of Razor to HP' s HP-UX and
- Silicon Graphics' IRIX under the Motif GUI. Razor also supports Solaris and
- SunOS.
- GlobalTrack is free of charge to registered Razor users. Razor is priced at
- $495 for a single floating license. An evaluation copy of the Razor manual is
- also available. For more information contact Tower Concepts, Inc., 103 Sylvan
- Way, New Hartford, NY 13413, (315) 724-3540; E-mail: razor-manual@tower.com or
- ftp.uu.net.
-
-
- HockWare Releases VisPro/C and VisPro/C++
-
-
- HockWare Incorporated has released two OS/2 GUI development tools, VisPro/C
- and VisPro/C++. Modeled after Vis-Pro/REXX, a visual REXX programming tool for
- OS/2 2.x, VisPro/C and VisPro/C++ are object-oriented drag and drop tools for
- IBM's User Interface Class Library and CSet compilers (CSet, CSet++, and
- CSet++ FirstStep). VisPro/C and VisPro/C++ provide an integrated environment
- that includes: the Build Options Editor, which allows programmers to visually
- set compiler and linker options; the Build Monitor, which lets programmers
- monitor the progress of compilation, linking, and resource compilation; and
- the Resource Editor, which lets programmers define icons, bitmaps, and
- strings.
- VisPro/C and VisPro/C++ are integrated with WorkPlace Shell and generate
- non-proprietary royalty-free C/C++ code compatible across VisPro products.
- Other features of VisPro/C and VisPro/C++ include: a set of CUA '91 objects
- supporting 3-D business graphics, formatted entry fields, spreadsheet, clock,
- and calendar; a built-in visual DB2/2 database designer; a custom object
- builder based on the IBM SOM; and multiple development views.
- VisPro/C and VisPro/C++ are priced at $399 each. For more information contact
- HockWare, Inc,, 315 N. Academy St., Suite 100, Cary, NC 27513, (919) 380-0616;
- FAX: (199) 380-0757.
-
-
- ZGRAF Announces ZGRAF C++ Graph Toolkit
-
-
- ZGRAF Software Products has released the ZGRAF C++ Graph Toolkit for Windows
- and Windows NT. The ZGRAF C++ Graph Toolkit is a library of C++ routines for
- producing technical and business graphs. Graph styles include: X/Y, Bar, Pie,
- Area, Ribbon, Scatter, Polar, Log, 2-D Function, 3-D Surface, and Smith Chart.
- The library is object-oriented and uses C++, but both C and C++ users can use
- the product.
- The ZGRAF C++ Graph Toolkit is priced at $30 for the Personal Developer
- Version or at $45 for the Commercial Developer Version. The Personal Developer
- is for private usage only; the Commercial Developer Version gives professional
- developers an unlimited distribution license with no royalties. Both versions
- of the ZGRAF C++ Graph Toolkit include the C++ source code. For more
- information contact ZGRAF Software Products, 1831 Old Hickory Court, New
- Albany, IN 47150, (812) 949-9524; CompuServe: 70742,1356; Internet:
- jjakob@delphi.com.
-
-
- Excel Introduces Translator 1.0
-
-
- Excel Software has introduced the Translator 1.0 reengineering utility to
- enhance its suite of CASE tools. Translator 1.0 automates the generation of
- diagrams within MacAnalyst and MacDesigner CASE tools from existing source
- code. Translator 1.0 scans the source code and extracts information into a
- text file which can be imported into MacAnalyst and MacDesigner.
- Using Translator 1.0, a developer can document existing source by producing
- diagrams and data dictionary information. Diagrams provide double-click access
- to the related code. Object-oriented software written in C++ or Object Pascal
- is translated to class diagrams using Booch, OMT, Shlaer/Mellor, or
- Coad/Yourdon notation. The Class diagram illustrates each object class,
- inheritance structures, and its attributes and operations. Code written in C,
- Pascal, Basic, or FORTRAN can be used to generate structure charts. Diagrams
- can also be organized into multiple diagram levels.
- Translator 1.0 is priced at $495 for a single license. For more information
- contact Excel Software, P.O. Box 1414, Marshalltown, IA 50158, (515) 752-5359;
- FAX: (515) 752-2435; E-mail: casetools@aol.com.
-
-
- Vireo Announces VToolsD
-
-
- Vireo Software has announced VToolsD for Windows 3.1, a C/C++ toolkit for
- developing Virtual Device Drivers (VxD). VToolsD includes QuickVxD, which
- offers a "visual programming" model of VxD development, letting developers
- create new VxDs by using the mouse. Libraries included with VToolsD enable VxD
- developers to use Microsoft Visual C/C++ 32-bit Edition. The VToolsD C++ Class
- Libraries provide a set of classes for those who prefer an object-oriented
- framework for VxD development. On-line help, examples, and source code for all
- of the VToolsD libraries are included.
- VToolsD for Windows 3.1 is priced at $495. For more information contact Vireo
- Software, Inc., 385 Long Hill Rd., Bolton, MA 01740, (508) 779-8352; FAX:
- (508) 779-8351; Internet: vireo@vireo.com.
-
-
-
- Pentek Introduces File Transfer Language
-
-
- Pentek, Inc. has introduced File Transfer Language (FTL), a SCSI
- file-management system, plus a collection of software libraries for
- initializing, organizing, sorting, and retrieving data on SCSI devices. FTL
- supports 21 SCSI devices connected to a VMEbus-based DSP and data acquisition
- subsystem using Pentek's MIX-interface mezzanine boards. The SCSI hardware
- interface is provided by the Model 4255 based on the MIX interface.
- FTL consists of a UNIX-like multitasking environment with function libraries
- that provide subroutines for using the SCSI file system. FTL supports two
- programming environments, C and SPOX. The C libraries are subroutines that can
- be used for single-threaded C programming or as a header file that may be
- embedded in custom DSP applications. SPOX supports multitasking and provides
- streaming drivers. SPOX also includes a modular and portable real-time
- operating system for C, workstation interfaces, and a library of DSP
- functions. The FTL also supports Pentek's SwiftNet, based on the TCP/IP
- interface for Ethernet.
- The FTL is priced at $2,000 and Model 4255 is priced at $1,795. For more
- information contact Pentek, Inc., 55 Walnut St., Norwood, NJ 07648, (201)
- 767-7100; FAX: (201) 767-3994.
-
-
- TGS Ships OpenGL DHA
-
-
- Template Graphics Software Inc. has begun shipping the beta version of OpenGL
- DHA for Solaris and Open Inventor for Solaris. OpenGL is a 3-D graphics
- software system licensed from Silicon Graphics. Open Inventor is a C++ class
- library based upon OpenGL and is used for 3-D graphics application
- development. OpenGL DHA for Solaris is a direct port of OpenGL to Sun. The
- beta release is an OpenGL feature and conformance release for Sun GX and ZX
- hardware.
- For more information contact Template Graphics Software, Inc., 9920 Pacific
- Heights Boulevard, Suite 200, San Diego, CA 92121, (619) 457-5359; FAX: (619)
- 452-2547.
-
-
- ISE Ships EiffelNet
-
-
- Interactive Software Engineering, Inc. has begun shipping EiffelNet, a
- client-server, application-development communication system. Using EiffelNet,
- developers can build applications that exchange objects asynchronously over a
- network using sockets. EiffelNet is a part of ISE Eiffel, an object-oriented
- graphical development environment that is compatible with existing C software.
- The Eiffel workbench generates stand-alone ANSI C. EiffelNet follows the
- structure of other ISEEiffel libraries, and fits in the overall hierarchy the
- fundamental library of data structures and algorithms of ISE Eiffel 3.
- EiffelNet provides users with predefined schemes corresponding to the most
- commonly occurring client-server cases. An application can then include a
- client class and a server class that inherit from the corresponding predefined
- classes. New classes can then build the specific objects to be exchanged and
- specify the required processing. Communication and synchronization aspects are
- handled automatically by the predefined classes. More specific classes are
- also available for applications that need a finer degree of control of the
- actual underlying mechanisms.
- For more information contact Interactive Software Engineering Inc., 270 Storke
- Rd., Suite 7, Goleta, CA 93117, (805) 685-1006; FAX: (805) 685-6869; E-mail:
- info@eiffel.com.
-
-
- Cadre Technologies Announces ObjectTeam/OOA Sim And Professional Services
-
-
- Cadre Technologies Inc. has announced ObjectTeam/OOA Sim, a simulation and
- verification tool for their ObjectTeam for Shlaer-Mellor tool suite.
- ObjectTeam/OOA Sim lets developers verify, simulate, and prototype their
- applications at the early stage of the development cycle. ObjectTeam/OOA Sim's
- Motif-based GUI lets developers design test scenarios, execute the scenarios
- while viewing the results as they happen, set break points, and display
- summary information. ObjectTeam/OOA also supports incremental changes.
- ObjectTeam/OOA Sim can simulate multiple subsystems and provide support for
- both state transition diagrams and action data flow diagrams. ObjectTeam/OOA
- Sim uses a high-level, object-oriented language that supports the
- Shlaer-Mellor method's syntax and semantics.
- In another notice, Cadre announced Professional Services to support their
- development tools. Services include: product support, offering installation
- assistance, integration services, and implementation support; education
- services consisting of product and methodology training; and consulting and
- project management services. Classes are offered on-site or at Cadre's
- corporate training centers.
- Pricing for ObjectTeam/OOA Sim starts at $10,000 per network. For more
- information contact Cadre Technologies Inc., 222 Richmond St., Providence, RI
- 02903, (401) 351-5950; FAX: (401) 455-6800.
-
-
- Blinkinc Introduces Multi-Lingual Blinker 3.0
-
-
- Blinkinc has introduced Multi-Lingual Blinker 3.0. Blinker 3.0, a royalty-free
- DOS extender, Windows linker, and DOS dynamic overlay linker, that supports
- English, German, Spanish, and Chinese. Foreign language versions of Blinker
- 3.0 include a translated technical reference manual and Norton Guide help
- file. Technical support is available in various languages.
- Blinker 3.0 requires an 8086 microprocessor (or higher) to link or run
- real-mode programs. Blinker 3.0's DOS extender requires an 80286
- microprocessor (or higher) to run protected-mode programs and is compatible
- with DPMI, VCPI, and XMS specifications. Protected-mode programs will run
- under Windows, OS/2, and DOS.
- Blinker 3.0 is priced at $299. For more information contact Blinkinc, 8001 W.
- Broad St., Richmond, VA 23294, (804) 747-6700; FAX: (804) 747-4200; BBS: (804)
- 747-73333.
-
-
- QSi Ships ProTEST
-
-
- Quality Systems International, Inc. has begun shipping ProTEST, a test
- engineering tool which provides cross-platform portability and multiple
- database access; a character or graphical user interface including Windows
- v3.1; multi-user, on-line access and execution of test cases; an interface
- between automated test tools and problem error tracking; and standard
- management reports, along with integrated query and report writer.
- Other features of ProTEST include: ability to link requirements to test cases
- or test steps, user defined criteria for the test cases, priority setting for
- test cases, user alerts for test cases requiring attention, and test status
- summaries with calculations of the pass/fail percentage. ProTEST also includes
- seven methods for printing test cases.
- For more information contact Quality Systems International, Inc., 416-420
- Highland Ave., Cheshire, CT 06410, (800) 557-9787 or (203) 699-9787; FAX:
- (203) 699-9789.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- We Have Mail
- Dear C/C++ Users Journal:
- I just bought the September 1994 issue of your magazine. On page 19, David
- Singleton has an article on Windows memory management. However, this article
- is so full of bugs as an article on this subject can possibly get. The first
- line of the article states that "It is generally recomended that programs
- developed to run under MS-Windows use the medium memory model." This is just
- plain wrong. The recomended memory model for Windows programs is large. Medium
- model was for real mode Windows.
- But probably the worst error is the article's emphasis on GlobalLock and
- GlobalUnlock. These functions are only needed for real mode Windows, and as we
- all know, real mode Windows disappeared along with Windows 3.0. Windows 3.1
- can move memory at any time without invalidating pointers. This, of course, is
- due to its use of a Local Descriptor Table (LDT) to translate selector:offset
- to a logical address.
- In fact, Microsoft now recomends using malloc/free or new/delete (which maps
- to malloc/free) for memory allocations. Both Visual C++ and Borland C++, and
- probably the rest of the Windows C/C++ compilers, use a suballocation scheme
- in malloc, similar to the one presented in the article. But even though the
- article assumes real mode Windows, it actually requires protected mode to
- work. On page 25, FarHeapBlock::GetHbFromHandle is defined as follows:
- FP_FHB FarHeapBlock::GetHbFromHandle(HGLOBAL h)
- {
- FP_FHB p fhb = (FP FHB) GlobalLock (h);
- ASSERT (p_fhb != NULL);
- GlobalUnlock (h);
- return p_fhb;
- }
- The GlobalUnlock call actually tells Windows that it's okay to move the block.
- The pointer returned from the function is therefore invalid! It can be used
- safely only in standard or enhanced mode, and in real mode only until the next
- GetMessage or PeekMessage call. I find it amazing that a magazine starts off
- its Windows Programming issue with such an outdated and erroneous article. The
- only thing I got out of it was the name of a magazine to stop buying, and the
- name of a consultant firm to avoid.
- Martin Larsson, author of several unknown
- utilities for DOS and Windows
- e-mail: larsson@cs.colorado.edu
- David Singleton replies:
- Dear Editor,
- Thank you for sending me a copy of Martin Larsson's letter. Interesting and
- provocative reading. I give my response below, interlaced with excerpts from
- Martin Larsson's original text [in italics]. In the interests of courtesy, I
- am copying this reply to Larsson. If he would like to engage in further dialog
- direct with me, I would be delighted to respond.
- The recomended memory model for Windows programs is large. Medium model was
- for real mode Windows.
- I would ask Larsson to justify his statement that the recommended model for
- Windows Programming is large. My justifications for my statement are (in no
- particular order of importance):
- a. There are probably still some people out there using Windows 3.0. We do
- need to remember them.
- b. The medium memory model allows one to run more than one instance of a
- program. You cannot do this with the large model. (I will willingly concede
- that there may be occasions when it might be disastrous to allow more than one
- instance of a program to run.)
- c. The Visual C++ V1.5 AppWizard utility, when it builds a program framework,
- defaults to the medium memory model. It seems to me from this that Microsoft,
- themselves, endorse the use of the medium memory model as the preferred
- starting model.
- d. Medium memory models are generally smaller and faster because near pointers
- can be used to access the data in the local data segment.
- e. Finally, I would refer to Charles Petzold's book Programming Windows 3.1,
- published by Microsoft Press, Chapter 7, Page 285. His book is to all accounts
- something of an authority on Windows programming. Petzold says: "Windows
- programmers who require more than 64 KB of data in their programs might be
- feeling a little nervous at this point. They have a right to be because the
- compact and large models are not recommended for Windows programs. This does
- not mean they can't be used however."
- But probably the worst error is the article's emphasis on Global Lock and
- Global Unlock.
- Larsson has made a significant error in his statement concerning the use of
- GlobalLock. It is perfectly true that there is no longer the need to
- physically lock memory when using Windows 3.1. However, GlobalLock performs
- one essential additional function that Larsson seems to have forgotten. It
- translates the HGLOBAL returned by GlobalAlloc into a far pointer to the
- allocated memory. It is certainly Microsoft policy to use GlobalLock in
- conjunction with GlobalAlloc, as evidenced by the example code in Microsoft
- KnowledgeBase articles Q77226 and Q74197.
- In fact, Microsoft now recomends using malloc/free or new/delete (which maps
- to malloc/free) for memory allocations.
- If one is using a large memory model or a medium memory model with less than
- 64 KB of data, then, of course, one should use malloc/free or new/delete. It
- is by far the easiest way of getting more memory in these cases. However, if
- one wishes to use a medium memory model and have more than 64 KB of data, then
- one must use the Windows global memory services, as discussed in the article.
- But even though the article assumes real mode Windows, it actually requires
- protected mode to work.
- I regret that Larsson has again made a small error. I quote from the Microsoft
- SDK Programmers Reference Manual, Volume 2, concerning GlobalUnlock: "With
- movable or discardable memory, this function decrements the object's lock
- count. The object is completely unlocked and subject to moving or discarding
- if the lock count is decreased to zero."
- The emphasis above is mine. The marked phrase is the key to answering
- Larsson's concern. Each FarHeapBlock holds references to the previous and next
- FarHeapBlocks in the chain of FarHeapBlocks. I decided to hold these
- references as HGLOBALs. To get the corresponding address, I needed to use
- GlobalLock with an equivalent GlobalUnlock (it is always necessary to use
- these as a pair) to get the corresponding address.
- This is what the function FarHeapBlock:: GetHbFromHandle does for me. On entry
- to this routine, I would expect the relevant global heap lock count to be 1,
- as the HGLOBAL points to a block that has already been allocated. The call to
- GlobalLock will increase the lock count to 2. The subsequent call to
- GlobalUnlock will return the lock count to 1. As the function never causes the
- lock count to go to zero (and hence unlock the block), Larsson's comment is
- invalid
- I look forward to seeing Martin Larsson's response to my points. I am prepared
- to stand by my article and I believe that the above responses have refuted all
- of his comments on the article.
- Best wishes,
- David Singleton
- 100265,3625@compuserve.com
- Larsson replies:
- Here's my reply to David Singleton's comments.
- There are probably still some people out there using Windows 3.0. We do need
- to remember them.
- So? The large model runs on Windows 3.0 too doesn't it? I will agree that
- Windows 3.0 has a bug (how can I not?) that pagelocks all but the first
- segment if you have multiple data segments. So, there can be no movement of
- the segments. Not good. However, with Windows 95 coming up, I don't think we
- should limit ourselves by bugs in Windows 3.0. It's as simple as saying, "The
- program runs on Windows 3.0 and greater. However, if you experience 'out of
- memory' errors on Windows 3.0, you might want to upgrade to Windows 3.1.
- Windows 3.0 has a bug that..." You get the idea.
- Remember, this only applies to programs with multiple writeable data segments.
- And they can't use the medium model anyway. Also, remember that Windows is a
- large model program. So when you use the medium model, you're in fact dealing
- with mixed-model programming. This is, and has always been, a real pain since
- you have to remember which pointers are far and which are near.
- The medium memory model allows one to run more than one instance of a program.
- You cannot do this with the large model.
- This is simply not true. The limitation is that you cannot have more than one
- read-write segment, as long as all segments but one are read-only. Multiple
- instances works just fine in the large model (Programming at large, Dale
- Rogerson, Microsoft Developer Network Technology Group). MS-compilers from 8.0
- and up (VC++ 1.0), combine your data into one segment if possible. This means
- that your large model program automaticaly can run multiple instances. Of
- course, Borland and Watcom always did this.
- The Visual C++ V1.5 AppWizard utility, when it builds a program framework,
- defaults to the medium memory model. It seems to me from this that Microsoft,
- themselves, endorse the use of the medium memory model as the preferred
- starting model.
- Read, "The C/C++ Compiler Learus New Tricks," by Dale Rogerson, written Aug.
- 28, 1992, revised Jan. 27, 1993. The article is available on The Microsoft
- Developer Network. The conclusions is that Microsoft C/C++ version 7.0
- introduces new programming practices that facilitate the development of
- applications for Windows version 3.1 in protected mode. Programmers can now:
- Use the large memory model. Large-model programs are compatible with the
- protected modes of Windows version 3.1 and can have multiple instances.
- Use _fmalloc. With C/C++, _fmalloc, which is the large or model-independent
- version of malloc, performs subsegment allocation and conserves selector
- usage.
- Medium memory models are generally smaller and faster because near pointers
- can be used to access the data in the local data segment.
- Microsoft Systems Journal, 1993, Volume 8, Number 10, Questions & Answers,
- C/C++, last question, summary: "The upshot is: if you have a good C++ compiler
- that supports Windows, don't fret over memory management. But do use the large
- model... Of course, you can always get the memory in the medium model if you
- really want, by declaring everything far, and allocating everything with
- _fmalloc of farmalloc, but then you have all sorts of problems in C++ trying
- to allocate objects with new so their constructors get invoked.
- Why subject yourself to such torture? Anyone who tells you that performance is
- slower in the large model is probably a frustrated assembly-language
- programmer, not to be trusted. It's true, of course, it is slower. But not
- much. Your users will probably never notice. If a particular section of your
- code is performance-critical, you can always use near pointers, or even
- rewrite it in assembly. The large model is simply the easiest way to get gobs
- of memory."
- Yes, this is on C++, but most of it applies to plain C also. The large model
- just makes life so much easier.
- Finally, I would refer to Charles Petzold's book Programming Windows 3.1,
- published by Microsoft Press, Chapter 7, Page 285. His book is to all accounts
- something of an authority on Windows programming.
- I agree on one point, Petzold's book is a very good book on Windows
- programming. But, chapter 7 was not updated for 3.1. Therefore, it should not
- be accepted as the authority on Windows 3.1 memory management. I'm sorry, but
- Petzold's just plain wrong. Ask the guys on comp.
- os.ms-windows.programmer.memory what they think of Chapter 7 in Petzold's
- otherwise exellent book. You'll get the idea.
- Larsson has made a significant error in his statement concerning the use of
- GlobalLock.
- I guess I wasn't clear enough. My point is that there's no reason for locking
- and unlocking memory all the time. Lock once, when you allocate, using malloc,
- farmalloc, GlobalAllocPtr, or new. Unlock once, when you free the memory using
- free, farfree, GlobalFreePtr, or delete. Now, there are exceptions, just as
- with everything in Windows. For instance, you can allocate memory in a dialog
- procedure and return it to 'the calling function. However, since DialogBox
- only returns an integer, you'll have to return a handle (16-bit). You could of
- course use a global or member data in C++. Or you could send a message to the
- parent of the dialog.
-
- As the function never causes the lock count to go to zero (and hence unlock
- the block), Larsson's comment is invalid.
- So, what you're saying is that you call GlobalLock and GlobalUnlock totally
- unnecessary since you could just as well keep a copy of the pointer. Quite
- interesting when you're using the medium model to gain speed. And, you have
- memory locked all the time, quite contrary to what Petzold recommends. Of
- course, Petzold's wrong, memory can be locked for the whole duration of the
- program. But you seem to contradict yourself when you follow Petzold's advice
- on memory model, but not on when to lock and unlock the memory you allocate.
- I am prepared to stand by my article and I believe that the above responses
- have refuted all of his comments on the article.
- Well, I think I've made my points clearer. All references are to Microsoft
- Developer Network CD #8.
- Martin Larsson
- Singleton replies:
- Dear Larsson,
- Thanks for your last message. As the messages seem to be getting rather long,
- I will not go repeating everything. I will just repeat any necessary salient
- points.
- So? The large model runs on Windows 3.0 too doesn't it? I will agree that
- Windows 3.0 has a bug... You get the idea.
- Point noted, thanks
- Remember, this only applies to programs with multiple writeable data segments.
- And, they can't use the medium model anyways.
- Agreed.
- So when you use the medium model, you're in fact dealing with mixed-model
- programming. This is, and has always been, a real pain...
- Agreed. However, that is one nice thing about using MFC. Most of the time, one
- does not need to worry about near and far pointers, although there are, of
- course, occasions when one does need to remember which are which and that can,
- I agree, sometimes be a pain.
- The limitation is that you cannot have more than one read-write segment, as
- long as all segments but one is read-only.
- Correction accepted. That is really what I meant to say. In my article I was,
- though, dealing with the case where one would want more than one segment's
- worth of read-write data (i.e., more than 64 KB of data) and, in this case, my
- assertion that one cannot have more than one instance of a program is correct.
- Thanks for the references to the two Microsoft articles and for your comments
- on Chapter 7 of Petzold's books. I shall add them to my data bank.
- My point is that there's no reason for locking and unlocking memory all the
- time.
- I entirely agree with your comment. There is absolutely no need to lock and
- unlock memory all the time. You raised this comment with reference to my
- function FarHeapBlock::GetHbFromHandle. My reason for doing this is contained
- in the article, where I say, "I use doubly linked lists to keep track of
- individual FarHeapBlocks. Thus, a FarHeapBlock contains pointers both to the
- previous FarHeapBlock and to the next FarHeapBlock. In the case of
- FarHeapBlocks, I decided to store the HGLOBAL of the previous and next blocks,
- rather than a far pointer."
- Whenever I need to recover the actual address of a FarHeapBlock, I call
- FarHeapBlock::GetHbFromHandle, using GlobalLock and GlobalUnlock to do the
- necessary conversion. The fact that they set and release locks is, from this
- function's perspective, a side-effect. What I want is the HGLOBAL to FP_FHB
- conversion.
- The article then goes on to say, "With hindsight, I could have used far
- pointers instead. Indeed, this could probably give slightly more efficient
- code." I am sure you would say that I could delete 'probably' and 'slightly'
- from the above. With hindsight, I would agree with you.
- I will close by thanking you for this fascinating discussion. It is always
- good scientific and engineering practice to test one's arguments by open
- debate. I too use the large model when I consider it appropriate. However, I
- also use the method set out in my article, when that is appropriate.
- Best wishes,
- David Singleton
- In fairness to David Singleton, I should report that we sat on his article for
- a number of months before publishing it, then failed to check whether all the
- information was still timely. We indulge in such antics less and less often,
- and we check our accepted submissions better all the time, but we -- and our
- authors -- will never get it perfect.
- Windows, in particular, is a bottomless pit of complexity. The preceding
- discussion reminds me of numerous debates from the distant past over how best
- to perform various operations under various System/370 operating systems.
- Microsoft has reinvented IBM a quarter century later. I cross my fingers every
- time one of our authors describes one side of this latest elephant. They are
- more courageous than I am. -- pjp
- Bill,
- Re-October 1994 C/C++ UJ: yet another fine issue... I have mixed, mainly
- queasy, feelings over Bob Jervis's proposal for a (C++)--! It's true that C++
- has grown like Topsy and even the experts are confused. (See, for example, the
- theological altercations in any issue of The C++ Report.) But surely all
- programming languages have had similar growing pains as we've struggled to
- gain familiarity with "arcane" (i.e., new) concepts and features. Take the C
- declaration syntax (please). And consider that after 20 years or so, we still
- see "not quite accurate" CUJ articles using or explaining C
- arrays-as-pointers. If one of Bob's motivations is to ease the
- compiler-writer's burden, forget it. Compiler-writers, in rerum natura, thrive
- on impossible challenges -- specification ambiguities being especially
- welcomed (and provably unavoidable from the lessons of Algol 68). Otherwise,
- pjp would be editing The Machine-Language Users Journal. My suspicion is that
- as the C community mulls and fights over Bob's "minimalist" OOPL, his (C++)--
- will post-accrete "one damn good thing after another,"aping the agonizing
- stepwise refinement (some might call it unfinement) of Bjarne's original "C
- with Classes" (1979) into ANSI/ISO C++ (1994). The story is uniquely
- documented in The Design & Evolution of C++ (Bjarne Stroustrup,
- Addison-Wesley, 1994), which traces the pros and cons of each increment.
- Consider just one item not mentioned by Bob: the C++ "exposure" specifier
- protected. This public/private "compromise" was added for members in Release
- 1.2 and for base classes in Release 2.1. For member functions, Bjarne still
- considers protected to be a "fine way of specifying operations for use in
- derived classes." However, protected data members are now frowned on, even by
- Mark Lipton who pushed for the change (ibid, p. 302). Will Bob exclude
- protected completely as "inessential," or, learning from C++ experience,
- confine it to member functions. I smell a good twelve months argufying on this
- point alone. It seems that (C++)-- will never "catch up" since "All is Flux,"
- especially C++ itself. PAX, etc.
- Stan Kelly-Bootle
- Contributing Editor,
- UNIX Review and OS/2 Magazine
- PS: Re-your name: the Celtic diphthong au is usually pronounced ow, as in
- plow, not aw, as in plaudit. However, both sounds seem appropriate! BTW: Peter
- van Linden offers $1 for each error found in his Expert C Programming. I
- reported that he had spelled your name Plaugher on two occasions, but he only
- sent me one dollar!
- Whew! It might have been easier refereeing the previous discussion on Windows
- programming than replying to a classic Kelly-Bootle missive. (C++)-- is a cute
- name, but an invalid expression, in Standard C at least. More important, not
- even Jervis is proposing making C a full-fledged OOPL. The idea is to crib a
- bit of prior art that has proven utility at low cost in complexity, not to
- invent yet another language on the fly as part of the standardization process.
- As for my name, I'm told it originally hails from Alsace Lorraine. A slippery
- dipthong like au shifts neatly between French and German pronunciation,
- depending on who's in charge in a given year.
- Dear Sirs!
- My comments about the idea of the modifications to C mentioned in the article
- "All is Flux" (CUJ, October 1994 by B. Jervis): YES! YES! YES! YES! YES! YES!
- ... ad infinitum! The only problem with it: It may kill C++. Then again we
- (real everyday programmers) may need such extended C (maybe we should call
- C+-?) desparetely when C++ dies by itself under its own weight. C++ inflates
- at the rate approaching the rates of supernovas.
- Sure, it gives more and more employment for language commentators, critics,
- academic programming gurus, etc. (This is not an innuendo about PJP. Please
- believe me!). Anyone remember Algol 68? If we needed a language so big as the
- future C++ maybe we should settle for something already working and tested
- like Ada? C became so popular because it allowed people to program close
- enough to the performance levels of assembly language with benefits of the
- structure of a higher-level programming language. C++ became popular only
- because it is perceived as an extension of C!
- This is of course my personal view, but then again I am being told from time
- to time that true OOP can happen only in languages like Smalltalk and so on.
- B. Jervis! Go on!
- Bogdan M. Baudis
- Innuendo or no, I do get a certain job security from the ambitions of language
- designers. Still, I wish sometimes the job of explaining esoterica wasn't
- quite so hard. --pjp
- C/C++ Users Journal
- This is the second of two letters. The first, which is enclosed behind this
- one, was waiting to be stuffed in its envelope when my copy of the October
- issue of the Journal arrived. Needless to say, I read Bob Jervis's article and
- your sidebar with great interest. Having done that, I feel that what I said in
- my first letter concerning ADTs versus OOPing, and C's capacity to support new
- dialects, still needs saying -- perhaps more than ever.
- If classes are added to C, that's fine. That is, it is if they don't get in
- the way of their ADT forebears. My concern is that although there are many
- instances where another level of sophistication is needed, anything which
- creates the impression that OOPing is the way could stifle the growth of C as
- a source of new programming ideas. Also, if the freedom to create and expand
- ADT libraries is legislated against by committee fiat, I, for one, will not
- use any compiler implementing that standard.
- I've studied more than a few articles over the years on how to OOP in C,
- including the one Colvin did in CUJ last year. I've also "poured over" more
- than one book on the subject, and I have to say that I've yet to find any
- well-wrought discussion of the relationship between ADTs and classes as clone
- factories. The nail still protrudes!
- On a related point: In his Letter to the Editor in this October issue, someone
- named Marty says: "One of the best ways to become a better programmer is to
- read other people's code,..." This is a fairly common, often enunciated
- belief, but one which I find actually qualifies as a half-truth. Reading other
- people's code can also be one of the worst ways to learn. Not only are there
- many ways to write correct C, the ways that apear in print tend to perpetuate
- what might be called The-Old-Boys' version.
- A full-fledged discussion of C's several prevailing styles and the effect they
- have on program semantics ought to make an excellent subject for one of your
- columnist's columns. It ought also to fit-in nicely with your expressed intent
- to cover further discussion of C's ongoing development.
- Truly,
- Mark Rhyner
- Dear Mr. Plauger:
- I would like to suggest that a "read_only" data member access specification be
- added to the public, protected, and private access specifiers in the C++
- standard.
- I find myself writing numerous get_XXX functions to allow users of my classes
- to access the private data in them. The data is declared private to localize
- the responsibility and maintain the integrity of the data, but I still wish
- the callers to be able to act on the data. The only other alternative I can
- think of is to make the data public, which is okay if the users are
- disciplined about not altering the data.
- Problems with read_only might arise if the data exposed is a pointer, but that
- is the way of C anyway. If you have any suggestions on how to deal with this
- issue in C++ as it stands, I would be very interested.
- On another subject, I would be very interested in reading a regular column in
- The C/C++ Users Journal on programming style. It could cover issues such as
- the above:
- When to derive a new class as opposed to when a class should contain another
- class
- When should functions be declared virtual?
- What are the relative merits of iostreams compared to fprintf?
- What are the trade-offs among the possible design decisions?
- Sincerely,
- David Rosenbush
- I agree that read-only access to certain member objects could save some
- tedious writing. I also agree that style is an important topic. So far, we've
- kind of smeared responsibility for discussing issues of style among all our
- columnists. -- pjp
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Embedding on a Budget
-
-
- Jeff D. Pipkins
-
-
- Jeff D. Pipkins is a senior systems engineer at Compaq, where he writes
- firmware for intelligent option boards. He has been programming in C since
- 1985, and various assembly languages since 1979. His current interests include
- systems software tools, embedded OS's, and building small projects with
- embedded microcontrollers. He can be reached via Internet mail at
- pipkins@bangate.compaq.com.
-
-
-
-
- Introduction
-
-
- Let's say you want to burn some bits. You want to etch your code into the
- silicon, but you want to avoid massive collateral damage to your wallet.
- Fortunately, it is possible to embed C code into ROM without purchasing
- expensive, specialized tools. This small embedded project will get you
- started.
- To keep the cost down on the software tools, I used the Microsoft C compiler
- and assembler, simply because that's what I happened to have on disk at the
- time. You could use other compilers instead with a little modification to the
- code presented. You'll also need a device programmer to burn those bits into
- your PROM, EPROM, Flash ROM, or whatever you choose. I've seen them for as
- little as $130.
- I could have chosen an exotic target platform, but to conserve cash, I chose
- some inexpensive hardware that I had lying around the house. It's an old IBM
- XT, sporting a classic 8088 processor at 4.77 MHz. There actually are some
- good reasons for choosing such a machine, beyond simple economics. Several
- processors aimed at the embedded market are basically composed of an 8088 core
- augmented with on-chip devices. The 80188 has become well-established as an
- embedded processor, and it's available in a seemingly endless variety of
- incarnations: the 80C188XL, 80C188EA, 80C188EB, and 80C188EC, as well as
- low-voltage and 16-bit versions of these, some so new that this year's
- databook on them is marked "preliminary!" So if you want to get in on today's
- embedded world, just grab yesterday's computer dinosaur at a ridiculously low
- price and go for it!
-
-
- A Simple Project
-
-
- The purpose of this article is to show how to embed C code without expensive
- tools. I present a small program to be embedded (Listing 1), along with the
- other pieces needed to make it work. The program is a slight variation on the
- time-honored "Hello World," modified to work in an embedded environment.
- The built ROM image works without BIOS, MS-DOS, or anything else. It's
- completely self-sufficient, and takes control immediately when the machine is
- powered on.
- The make file (Listing 2) handles a large part of the embedding task in this
- project; it invokes the linker, exe2bin, and even debug to build a ROM image
- capable of sending a greeting out a serial port. I explain this process in
- detail later.
- Another piece of code is required to get an image running once it's embedded
- in ROM. This custom assembly-language startup code (Listing 3) sets up the
- run-time environment needed for hello.c to run. Rather than give a
- line-by-line account, I provide here a general explanation of what this code
- must do. Finally, I've written a really stripped-down version of stdio. c,
- which supports output to a serial port. My stdio.c, is not listed here, but is
- available on this month's code disk.
-
-
- Building the Program
-
-
- For many programmers, the build process is just a tedious detail. However, if
- you want to build a ROM image, you need to be just as skilled at building code
- as you are at writing it. After all, the compiler is just one tool in the
- chain. To build the ROM image, I also use the linker, exe2bin, and debug.
- Chances are you' ve used all of these before in a cookbook fashion, but now
- you'll need to take a closer look at what these tools actually do behind the
- scenes so you can use them for the atypical task of creating a bootable ROM
- image.
-
-
- Poor Man's Locator
-
-
- When you're writing MS-DOS programs, you usually don't have to worry about
- where your code will be loaded, or even whether it's relocatable. Relocatable
- code can be executed at any address, because it has no references to absolute
- addresses. COM files are good examples of relocatable code. You can just copy
- COM files anywhere, set up the segment registers, and jump in. The code in EXE
- files is not always relocatable -- it may contain absolute references to code
- and data. So how does MS-DOS load EXE files wherever it wants? The EXE file
- contains a relocation table (sometimes called a "fix-up" table). This table
- shows all of the absolute references in the image. After the loader copies the
- image into RAM at a particular location, it adds the segment address of that
- location to every absolute reference in the image. This process has been
- called "locating," "relocating," "performing fix-ups," or "address binding".
- (See Figure 1.)
- If an EXE file's relocation table is empty, then there are no absolute
- references, and the code is relocatable. Such a file can be converted into a
- COM file (which is just a straight, relocatable image) using the exe2bin
- program. (exe2bin is readily available; various versions have shipped with
- MSDOS, the MSDOS technical reference manual, MSC, and MASM.) exe2bin's
- original purpose was just to convert relocatable EXEs into COM files, and if
- the EXE file had any entries in its relocation table, exe2bin would just
- complain and give up. Later versions of exe2bin are more useful. If it finds
- entries in the relocation table, it prompts you for a base address, and then
- uses it to perform the fix-ups!
- So exe2bin now does basically the same thing that the MS-DOS loader does,
- except that it writes the resulting bound image to a file instead of executing
- it. exe2bin will serve as a "poor man's locator." A more fanciful locator
- would allow us to specify a different base address for each different segment,
- so that the image would not have to be contiguous. Since segment information
- is not contained in the EXE file, such a product generally has to do the
- linking as well just to keep that information. That's why they're sometimes
- called "Linker/Locators." An alternate approach might be to parse this
- information from a MAP file that's generated by the linker.
-
-
- Linking
-
-
- According to a tradition older than the 8088, the C compiler divides a program
- into segments. The compiler usually creates a code (or "text") segment, a data
- segment for ininitialized data, a "bss" segment for unitialized data, and a
- stack segment. MSC adds several other segments, such as a "null" segment, to
- which null pointers point, a "const" segment for constants, and various
- segments for relatively new inventions, such as the "far heap."
- The linker combines like segments from many object modules. (See Figure 2.)
- Even though each object module may have its own code segment, the linker
- combines them all into a single contiguous segment. In addition, the linker
- allows object modules to refer to code or data in other modules by linking
- external references together, which of course is where the linker gets its
- name.
- The linker also determines the order of the segments in the final image. In
- this project, the startup code must be the first object module for input to
- the linker (as shown in the make file, line 52). The linker maintains the same
- segment order that first appeared in the first file. I make use of this
- convention to control where the segments are loaded in relation to each other.
- Controlling this order is very important, since this project requires the
- segments to be in a different order than usual.
-
-
- Segment Order for Embedded Code
-
-
- Moving your code into ROM places additional constraints on where things need
- to be, and it's the job of the startup code (Listing 3) to see that everything
- is in its place.
- Before the C code executes, its data segments must be copied into RAM. The
- executable code must remain in ROM so that it won't consume RAM space. For
- this reason, all data must initially occupy a block located at the beginning
- (lowest) location in the ROM image, so that all data offsets will be correct
- when DS is set to the beginning of the RAM data area. When exe2bin asks for a
- fix-up base, the make file supplies the segment address of the RAM data area
- where the data will be after the startup code copies it. This will make far
- data pointers correct. Since the first 256 bytes of RAM are reserved for the
- interrupt vector table, I chose to put the RAM data area at 0x40:0, which is
- just above that.
- Since the code stays in ROM and the data is copied to RAM, the image becomes
- discontiguous. This creates a new problem in that now, instead of just one
- fix-up base, we really need two -- one for the code and one for the data.
- Unfortunately, exe2bin allows only one fix-up base to be specified because it
- assumes that the image will be contiguous when it executes. If we specify the
- RAM data area segment address as the fix-up base, then far code pointers will
- be incorrect, and if we specify the ROM as the fix-up base, then far data
- pointers will be incorrect. I deal with this dilemma by using the compact
- memory model, which allows up to 1 MB of data but only 64 KB of code. Thus,
- all generated code is relative to a single CS register value, which will work
- as long as the code contains no far calls or jumps (no far functions, and no
- far function pointers).
-
-
-
- More on Segment Order
-
-
- As already mentioned, a program's data segments must be copied to RAM before
- the program executes. The order in which these segments end up in RAM is also
- important. Most of the segments will belong to what is known as a group, which
- is a bunch of segments that will all be referenced using the same segment
- register at run time. The compiler assumes there will be a group called
- DGROUP, which is referenced by the DS register. The NULL segment is first so
- that DS:0 will point to it, and the BSS segment and stack are at the end, so
- that the near heap (if there is one) and stack can contend for space (this is
- called a "parasitic" heap, since it feeds on stack space). However, there is
- no near heap in this example. A heap exists only as supported by the library,
- and the program can't use the malloc that comes with the compiler because it
- depends on MS-DOS calls.
- If you need a heap, you can write your own malloc and free. Should you decide
- to do this, I'd recommend inserting another segment between BSS and stack to
- make it easier to reference the end of the BSS segment. For this example, the
- stack is part of DGROUP and SS == DS. If you prefer, you can throw a compiler
- switch that removes the SS == DS assumption, and then remove the stack from
- DGROUP. Removing the stack from DGROUP would give you more room for near
- variables, but you'd have to make sure you set up SS properly. Finally, the
- far data belongs after the stack, possibly out of reach of the DS register.
-
-
- Execution -- Eventually
-
-
- To me, part of the excitement of using an embedded system is being on your
- own. No power-on self test, BIOS, or OS will get control before your code.
- There's no one else to lean on. The processor comes to life, and you're in
- charge. This is all great fun, but it imposes still more constraints on how
- the program's constructed and loaded.
- When the processor powers up or resets, it begins execution at F000;FFF0,
- which is the last !6-byte paragraph of address space (see sidebar for
- differences in 286 or later processors). The ROM image must have code
- strategically located in this spot. Since the program has only 16 bytes to
- work with, it will have to jump elsewhere, and since elsewhere may be more
- than 64 KB away, the program must do a far jump. Recall, however, that far
- jumps won't work -- they won't get fixed-up correctly. So this time I just
- cheat by using debug to patch in a jump instruction to an absolute location.
- The jump requires no fix-up because the address is absolute, and exe2bin
- doesn't complain because I use debug after exe2bin (see Listing 2, line 74).
- Now the problem is, how can I supply an absolute code location for the far
- jump, since its location depends on where the data ends? I solve this problem
- by putting a small amount of code at the very beginning of the ROM image.
- Since the beginning of the ROM image is a constant, it's easy to insert a jump
- to that location. The data is also located at the beginning of the ROM image,
- and the NULL segment must be first. By default, when the MSC startup code and
- linker create the NULL segment they initialize the first 16 bytes with zeros.
- You could suppress these zeros (via a linker switch), but that's probably not
- a good idea. Having zeros at the beginning of the NULL segment makes a program
- a bit safer -- dereferencing a null pointer will access the NULL segment
- instead of the data segment, as long as the access is no more than 16 bytes.
- I've just given compelling reasons why both the NULL segment and the startup
- code should occupy the beginning of the image. Will this work? Luckily, yes.
- Unlike many processors, the 80x86 does not use a zero byte for a NOP
- instruction. Instead, two zero bytes form the "add [bx+si], al" instruction,
- so if the NULL segment is executed as code, it will appear to start with eight
- of these instructions. Executing these instructions causes no harm, since
- memory will be initialized afterwards anyway. I can place "real" code right
- after the eight adds. This code, which I call jumpstart code, is actually
- located in the NULL segment (I specify its location by enclosing it in a
- segment . . . ends block -- see Listing 3, lines 49 through 100) so that the
- linker can't possibly separate it from the 16 zeros.
- The jumpstart code just needs to accomplish one thing, a far jump to the
- startup code proper (_TEXT:0). That doesn't sound difficult, but recall that
- exe2bin will add a base address (in this case, 0x40) to all absolute
- references! It doesn't know the difference between absolute code references
- and absolute data references, it just adds! The reference will be wrong at
- execution time, but I know the base (0x40), so I can subtract it to undo the
- effect of the incorrect fix-up. This locates the reference with respect to
- zero, so I must also do the correct fix-up at run time to locate the reference
- with respect to the beginning of the ROM image. Since self-modifying code is
- out of the question (morals aside, we are executing in ROM), I use the first
- doubleword at DS:0 temporarily for a jump vector, and compute the correct
- address there. Since I'm using the compact memory model, I know that the
- compiler won't generate any far jumps, and the trouble ends here. From this
- point on, it's smooth sailing into the startup code, and eventually, into your
- program.
-
-
- Have Fun
-
-
- I've presented a dirt cheap way to develop programs on a PC and embed them in
- ROM. I hope you don't just read the code -- do something! Even if you don't
- have an embedded project in mind, doing something simple is worth it just for
- the experience and the education you'll get on the way. So snag yourself some
- hardware, any hardware, and teach it to say hello to the embedded world.
- ROM Startup on 80286 or Later Processors
- If you just want to use a 80286 or better as a fast 8088, (in real mode, that
- is, since you need more advanced software tools than the ones presented here
- if you want to get into protected mode) you must be aware of some subtle
- differences in its reset process.
- When execution begins on a 80286 or higher, CS:IP will be F000:FFF0, just as
- for the 8088, but address lines 20 and higher will stay high until the first
- time the CS register is loaded. So execution begins at the top of the address
- space, but if you do a far jump, or otherwise cause CS to be loaded, you'll
- suddenly find yourself down in the first 1MB of address space! On most
- architectures, the ROM is reflected into the top of the 1MB address space by
- the address decode circuitry so that this isn't a problem. If your hardware
- doesn't reflect the ROM, then you should use only near jumps, initialize
- briefly, and use protected mode -- in fact you should get into protected mode
- as soon as possible.
- Figure 1 The loader adds the base address of the image to each of the
- references mentioned in the relocation table.
- Figure 2 The linker combines like segments from many object files. The segment
- order is determined from the first object file.
-
- Listing 1 The application to be embedded
- /*===========================================================*\
-
- hello.c
-
- \*-----------------------------------------------------------*/
-
- #include "stdio.h"
-
- #define PUBLIC
- #define PRIVATE static
-
- /*===========================================================*\
-
- main()
-
- \*-----------------------------------------------------------*/
-
- PUBLIC void main(void)
- {
-
- /*--------------------------------------------------------*\
- Initialization
- \*--------------------------------------------------------*/
-
- init_stdio();
-
- /*--------------------------------------------------------*\
- Display a greeting...
- \*--------------------------------------------------------*/
-
-
- puts("Hello, embedded world!");
-
- /*--------------------------------------------------------*\
- If you return from main(). the startup code should
- invoke a reset...
- \*--------------------------------------------------------*/
-
- puts("About to reset. Goodbye!");
-
- }
-
- /*===========================================================*\
- End of source file.
-
- \*===========================================================*/
-
- /* End of File */
-
- Listing 2 The make file
- ########################################################################
- #
- # makefile for embedded "hello.c" application.
- #
- # MSC 7.0 Compiler Switches
- #
- # /c - compile only. no link step
- # /AC - Specifies compact memory model
- # /FPi87 - Generate in-linex87 code (but our code uses no fp instructions)
- # /Gs - Remove Stack Overflow Checks
- # /Ois - Optimize for code size over execution time.
- # The 'i' allows certain functions (inp, outp, etc.) to be
- # generated inline. Just say NO to unsafe optimization options!
- # /W3 - Almost maximum warning level
- # /Fc - Generate mixed C/asm listing to specified file
- #
- #
- # LINKING
- #
- # Our code doesn't use any floating point variables at all, so
- # it shouldn't need any floating point support. The -FPi87 option
- # is used, and the clibc7 library is used just to make sure that
- # no floating-point emulator code is included in the build.
- #
- ########################################################################
- CFLAGS = /c /AC /Gs /Ois /FPi87 /W3
-
- .c.obj:
- cl $(CFLAGS) -Fc$*.cod $*.c
-
- .asm.obj:
- masm /MX $*.asm,$*.obj.$*.1st;
-
- default: embed.bin
-
- #
- # Source to be compiled or assembled.
- #
-
- hello.obj: hello.c stdio.h
-
-
- stdio.obj: stdio.c stdio.h
-
- startup.obj: startup.asm
-
- #
- # Link step -- NOTE: startup MUST be linked first.
- #
-
- embed.exe: startup.obj hello.ebj stdio.obj
- link startup hello stdio, embed.exe, embed.map /NOI/MAP/NOPACKCODE;
-
- #
- # In this step. we use exe2bin as a locator. It will ask for a
- # segment base to use for fix-ups. We specify the segment address
- # in RAM where the data will be during execution. The startup
- # code will use the value specified here to decide where to copy
- # the data before beginning execution. Value specified is in hex.
- #
-
- embed.fix: embed.exe
- exe2bin embed.exe embed.fix < <<
- 40
- <<
-
- #
- # This step uses debug to insert code into the reset vector.
- # Note that debug will load the first byte at offset 100h instead
- # of offset 0. The instructions inserted are CLI, CLD, and JMP F800:0
- #
-
- embed.bin: embed.fix
- debug embed.fix < <<
- n embed.bin
- r cx
- 8000
- e 80f0 fa fc ea 00 00 00 f8 00 00 00 00 00 00 00 00 00
- w
- q
- <<
-
- #
- # end of makefile
- #
-
- Listing 3 Startup code
- ;======================================================================
- ;
- ; Start-up code for embedded C programs
- ;
- ; intended for use with Microsoft C, but porting it to Borland C
- ; or other similar compilers shouldn't be too difficult.
- ;
- ; Donated to the public domain by Jeff D. Pipkins, who of course is
- : not liable for damages...
- ;
- ;
- ; This file MUST be linked FIRST. The linker will combine segments
- ; with the same class name together, and it will order those combined
-
- ; segments in the order that they first appear in this file. This
- ; is extremely important. It is always the first file the linker
- ; sees that gets to choose the ordering of the segments.
- ;
- ;----------------------------------------------------------------------
- ROMSEG EQU 0F800h ; beginning of ROM
- STACKSIZE EQU 14096
-
- ;----------------------------------------------------------------------
- ; data group (to fit in 64K)
- ;
- ; For our model, we need the data to appear first in the image,
- ; with the code up above it. Unfortunately, we also need for
- ; execution to begin at a fixed location known at build time,
- ; and exe2bin wants that location to be either at offset 0 or
- ; 100h from the beginning of the image. We solve this problem
- ; by putting a little code in the front of the data segment that
- ; can figure out where to jump.
- ;
- ;----------------------------------------------------------------------
-
- DGROUP group NOTSTACK, NULL/, CONST/, _DATA/, _BSS, STACK
- assume cs:DGROUP, ds:DGROUP
- public jumpstart
-
- ;The linker complains if there's no stack, and exe2bin
- ; complains if there is. This segment is here to fool
- ; the linker. See also the STACK segment. where exe2bin
- ; is fooled.
-
- NOTSTACK segment byte stack 'NOTSTACK'
- ; This must remain empty.
- NUTSTACK ends
-
-
- NULL segment para public 'NULL'
-
- ; The following bytes are the first bytes in the image.
- ; MSC wants 16 bytes of 0's here.
- ; We need to begin execution here as well...
-
- jumpstart:
- dw 8 dup (0) ; 8 x add [bx+si], al
-
- ; There's already code in the reset vector to
- ; do a cli & cld, but it's here again just for
- ; emphasis, and more importantly, in case somebody
- ; decides to modify the reset vector in the future.
-
- cli ; interrupts off -- no stack yet, no vectors yet.
- cld ; initialize direction flag
-
- ; Compute segment address of "startup" label
- ; The build process "locates" (provides fix-ups for) the
- ; image so that it will run properly if it is loaded into
- ; a particular address in RAM. This is what we want, since
- ; our data will be copied into RAM. Unfortunately, we
- ; really need for our code segment to be "located" with
- ; respect to the beginning of ROM instead. We resolve
-
- ; this dilemma in favor of the data, and then we'll set
- ; the value of CS by hand. This way, the code won't
- ; know that it's in the wrong place. This illusion will
- ; all crash if anyone does a far jump, since the value
- ; for CS will have been fixed-up incorrectly! So, no
- : far jumps anywhere! The only exception to this follows.
- ; We get away with it because we compensate for the
- ; miscalculation in advance. (All of this hokey stuff is
- ; a consequence of using exe2bin for a locator.)
-
- mov dx, _TEXT ; Segment address of code segment
- mov ax, ROMSEG ; Should be same as CS for right now
- add dx, ax ; Add fix-up for code segment
- mov ax, DGROUP ; Segment address where data will be
- sub dx, ax ; undo fix-up for data segment in RAM
-
- ; Temporarily use dword at DGROUP:0 to hold vector for jump.
-
- mov ds, ax ; DGROUP
- xor ax, ax
- mov word ptr ds:[0], ax
- mov word ptr ds:[2], dx ; computed segment for jump
-
- jmp dword ptr ds:[0] ; to "startup"
-
- ; and now back to your regularly scheduled data...
-
- NULL ends
-
- ; Variables declared as "const" will go here.
-
- CONST segment word public 'CONST'
- CONST ends
-
- ; Initialized global and static variables will go here.
-
- _DATA segment word public 'DATA'
- public __acrtused, _errno
- __acrtused dw 0 ; prevents linking in Microsoft's
- _errno dw 0 ; standard start-up code &libs
- _DATA ends
-
- ; Uninitialized global and static variables will go here.
- ; The C language guarantees that variables in this segment
- ; will be initialized to 0 at load time. It is up to the
- ; startup code to make good on this promise. Since we
- ; are using exe2bin, it will fill in segments like this
- ; one with zeros, but in many other environments, this
- ; has to be done explicitly. If you change the build
- ; process so that this becomes necessary, perhaps the
- ; easiest way to deal with it is to just begin by filling
- ; all of RAM with zeros. That way you'll also initialize
- ; the FAR_BSS and HUGE_BSS at the same time, and you won't
- ; have to figure out where any of them begin or end.
-
- _BSS segment word public 'BSS'
- _BSS ends
-
- ; Note: exe2bin doesn't want to see a stack segment because
-
- ; it knows the loader won't be able to set up the stack as
- ; specified. This startup code will set up this stack for us,
- ; but we need to trick exe2bin so that it won't know this is
- ; a stack. So we define it as PUBLIC instead of STACK.
-
- STACK segment word public 'STACK'
- public __stackend
- db STACKSIZE/8 dup ("<STACK> ")
- __stackend label word
- STACK ends
-
- ;----------------------------------------------------------------------
- ; data segments that are not part of dgroup
- ;----------------------------------------------------------------------
-
- FAR_DATA_START segment para public 'FAR_DATA'
- FAR_DATA_START ENDS
-
- FAR_BSS_START segment para public 'FAR_BSS'
- FAR_BSS_START ends
-
- HUGE_BSS_START segment para public 'HUGE_BSS'
- HUGE_BSS_START ends
-
- ;----------------------------------------------------------------------
- ; mark the end of all the data in the image
- ;----------------------------------------------------------------------
-
- ENDDATAIMAGE segment para public 'ENDDATAIMAGE'
- public __enddataimage
- __enddataimage label byte
- ENDDATAIMAGE ends
-
- ;----------------------------------------------------------------------
- ; Tell linker where to put the code segment.
- ;----------------------------------------------------------------------
-
- _TEXT segment para public 'CODE'
- _TEXT ends
-
- ;----------------------------------------------------------------------
- ; mark the end of the entire image
- ;----------------------------------------------------------------------
-
- ENDIMAGE segment para public 'ENDIMAGE'
- public __endimage
- __endimage label byte
- ENDIMAGE ends
-
- ;======================================================================
- ;
- ; Start-up code
- ;
- ;----------------------------------------------------------------------
-
- _TEXT segment
- assume CS:_TEXT, DS:DGROUP, SS:DGROUP
-
- extrn _main:near
-
- public startup
- startup:
- ;----------------------------------------------------------
- ; Initialize interrupt vectors to point to the restart vector.
- ; This will cause a restart on any unexpected interrupt.
- ;----------------------------------------------------------
-
- xor ax, ax
- mov ds, ax
- xor bx, bx
- mov cx, 256
- init_int_loop:
- mov [bx+0], 0FFF0h ; offset
- mov [bx+2], 0F00h ; segment
- add bx, 4
- loop init_int_loop
-
- ;----------------------------------------------------------
- ; Set up a temporary stack
- ;----------------------------------------------------------
-
- mov ax, DGROUP
- mov ds, ax
- mov bx, offset DGROUP:__stackend
- and bx, NOT 1 ; align sp on word boundary
- mov ss, ax ; DGROUP
- mov sp, bx ; __stackend
-
- ;----------------------------------------------------------
- ; Copy data image from ROM to RAM
- ;
- ; For later processors with protected mode, this code assumes
- ; that the ROM is reflected down into the top of the first
- ; 1MB of address space.
- ;----------------------------------------------------------
-
- mov ax, DGROUP
- mov es, ax
- mov dx, seg __enddataimage
- sub dx, ax ; number of paragraphs to copy
- mov ax, ROMSEG
- mov ds, ax
- copyloop:
- xor si, si
- xor di, di
- mov cx, 8
- rep movsw
-
- mov ax, es ;\
- inc ax ; > inc es
- mov es, ax ;/
-
- mov ax, ds ;\
- inc ax ; > inc ds
- mov ds, ax ;/
-
- dec dx
- jnz copyloop
-
-
- ;----------------------------------------------------------
- ; setup segment registers
- ;----------------------------------------------------------
-
- mov ax, DGROUP
- mov ds, ax
- mov es, ax
-
- ;----------------------------------------------------------
- ; setup C stack and first frame
- ;----------------------------------------------------------
-
- mov bx, offset DGROUP:__stackend
- and bx, NOT 1 ; align sp on word boundary
- mov ss, ax ; DGROUP
- mov sp, bx ; __stackend
- xor bp, bp ; frame 0
- push bp, sp ; mark it on the stack
- move bp, sp ; bp always has current frame pointer
-
- ;----------------------------------------------------------
- ; Call main()
- ; note: CLI, CLD on entry.
- ;----------------------------------------------------------
-
- call _main
-
- ;----------------------------------------------------------
- ; For embedded apps, main() should never return.
- ; If for some reason it does, we'll start all over
- ;----------------------------------------------------------
-
- ; (for some reason, it's difficult to convince
- ; the assembler to generate this instruction.)
- db 0EAh ; jmp far
- dd 0F000FFF0h ; restart vector
-
- _TEXT ends
-
- ;=======================================================================
- ;
- ; Set entry point to beginning of image.
- ; (This is done mostly to satisfy the assembler and exe2bin.)
- ;
- ------------------------------------------------------------------------
- end jumpstart
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Programming Flash Memory
-
-
- Mike Cepek
-
-
- Mike is a Senior Software Design Engineer in the Research and Development
- department of Management Graphics, Inc., Minneapolis.Mike started programming
- in the 7th grader, and later earned his B.S. in Computer Science at the
- University of Minnesota/Institute of Technology in 1984. Mike can be reached
- on the Internet at cepek@mgi.com.
-
-
-
-
- Introduction
-
-
- Flash memory is an increasingly popular choice over EPROMs for system
- designers, especially for embedded systems. The devices available today allow
- in-circuit reprogramming of megabytes of "ROM" with few or no additional
- parts. This article describes the additional software required to program
- these devices.
-
-
- Why We Like Flash
-
-
- One major benefit from flash is faster software development turnaround time.
- Burning and installing EPROMs for each new version of firmware slows down
- development. While downloading the firmware to RAM can eliminate these steps,
- it assumes that we have adequate (i.e. more) RAM for the task, which is not
- always a good assumption. This is because in an embedded system, code and
- static data usually reside in ROM. Note also that the ROM and RAM versions of
- the code are not identical -- the ROM and RAM address spaces will be
- different, leading to different firmware relocation addresses. Using flash
- memory avoids all these development issues.
- Our embedded systems currently use two types of flash memory devices made by
- AMD: the Am29F010 and Am29F040. These devices are single-voltage (5v),
- byte-wide memories with 128 KB and 512 KB capacity, respectively. High storage
- capacity is just one of the many device features available in flash memories
- (see sidebar). For example, both of these devices divide their memory into
- eight equal size sectors, each of which can be independently erased and
- reprogrammed.
- These two flash devices are compatible with the JEDEC pinout standard for
- EPROM devices. This allows us to use flash during the development process and
- to use off-the-shelf EPROMs in production where desired.
- Our customers also appreciate flash because it enables them to install
- firmware updates without disassembling the product to replace EPROMs. We have
- even provided "media-less" firmware updates to customers via e-mail, which
- they can download to the product and save in the flash memory.
- [1] and [2] discuss other applications for flash, and discuss common problems
- with and solutions for embedded system flash implementations.
-
-
- Implementation Background
-
-
- Code written for embedded systems is often highly specific to one platform.
- However, by applying principles of generality and modularization, I believe we
- have produced code that is reasonably portable and adaptable.
- Our implementation uses two byte-wide flash chips in parallel, allowing direct
- 16-bit reads and writes with minimum access time (see Figure 1). This leads to
- some interesting program timing constraints, to be covered later in this
- article.
- The remainder of this article discusses the production code we use with these
- devices. I describe the data structures defined in the file fmutl.h first, and
- follow with a bottom-up presentation of the routines, defined in fmutl.c.
- (These two source files are available on this month's code disk. The disk
- includes another source module which uses these routines for diagnostic and
- unit testing.)
-
-
- Data Structures
-
-
- Listing 1 shows the header file, fmutl.h. It contains the following:
- typedefs for the FMINFO and FMERR structures
- an extern for an FMERR instance
- prototypes for the global routines in fmutl.c
- The FMINFO structure holds information describing the characteristics of the
- flash devices. This data allows the code to adapt to different flash devices
- still having similar overall characteristics (e.g., programming algorithm).
- The fm_status routine fills in the FMINFO member data.
- The FMERR structure provides error information to the calling routine. A
- global instance of this structure is defined in the fmutl. c module, and
- fMerr. h provides an extern declaration.
- Listing 2 shows the manifest constants and static variable definitions for the
- program. (On the code disk, Listing 2 through Listing 7 comprise fmutl. c.)
- Because our code accesses two byte-wide devices in parallel, we've duplicated
- the command code bytes in the command constants -- the commands will be
- written to, and data will be read back from, both devices simultaneously.
- The constants FM_DATA1 and FM_DATA2 are offsets from the beginning (base) of
- the flash memory space, and will be resolved to actual system addresses in the
- flash memory address space at run time. The global definition of the fm_err
- structure allows fmutl. c to export error information to calling routines.
- DevTable is an array of FMINFO structures that describes the flash devices
- supported by the program. The code uses this data at runtime to accommodate a
- variety of devices. (One of our product models actually does use two sizes of
- flash devices in the same unit.)
-
-
- Flash Programming Routines
-
-
- The routines in fmutl.c are ordered in traditional bottom-up-first fashion.
- Accordingly, I begin my discussion with the low-level routines, followed by
- mid- and high-level routines.
- The fm_error routine (Listing 3) needs little explanation. It simply stores
- its parameters in the global fm_err structure. We make this routine global to
- allow calling modules to handle flash-related errors without duplication of
- code. For example, a diagnostic test verifying data written to the flash could
- handle a verification error by calling fm_error with its own unique error
- code.
-
-
-
- Picking the lock
-
-
- In normal operation, flash memory behaves like ROM: values may be read from
- ROM but writes are ignored. Attempts to write to ROM are usually programming
- errors, and are thus rare. Flash devices take advantage of this rarity by
- allowing for a series of special writes to act as "key." When a flash device
- recognizes the predetermined sequence, it becomes available for erasing,
- writing, and other operations.
- The fm_cmd routine (Listing 3) recites the magic incantation to unlock the
- devices into the special mode selected by the third parameter. If any of the
- writes contain unexpected data, the devices revert to read-only mode.
- We use the static variable pBase to simplify parameter passing. pBase is
- initialized by the two global routines fm_status and fm_write, to point to the
- base address of the flash memory space. (In our products with two sets of
- flash devices, one set starts at system address 0x00000000, the other at
- 0x64000000.)
- Because flash memory is implemented in the system as normal read/write memory,
- there are no special timing considerations during normal operation. We can use
- normal memory read and write accesses from high-level code to perform all the
- special device functions.
-
-
- Mid-Level Routines
-
-
- Listing 4 shows the routines fm_protected and fm_status. The former uses a
- special mode of the devices to query if any of the memory sectors are
- protected. Protected sectors cannot be erased or reprogrammed, and typically
- store bootstrap or BIOS type code. (We could have implemented the fields in
- FMINFO for reporting protected sector information as bits within a word or a
- long, but this would place an artificial limit on the number of sectors in a
- device. Some flash devices have as many as 1024 sectors.)
- FMINFO represents the protected sectors using a start sector and number of
- sectors. This method assumes that the case of non-contiguous protected sectors
- is not interesting, since bootstrap and BIOS code is usually in a single area
- located at the highest or lowest area of memory. We use the shift-right
- operator, as found in the first two lines, throughout this module to convert
- byte lengths to word lengths, since all pointer arithmetic is word-based.
- To sense which sectors are protected, fm_protected calls fm_cmd to place the
- devices in AutoSelect mode. In this mode, reads from certain offsets in the
- flash address space will return chip status information instead of real data.
- The constants with the FM_AO_prefix identify these special address offsets.
- Inside the loop, the routine reads a word from each sector. The
- FM_PROT_SECT_MASk bits within the word indicate whether that sector is
- protected in either device.
- The fm_status routine is global. It uses AutoSelect mode to read back ID bytes
- indicating the device manufacturer and the device type. The routine scans the
- DevTable array of known device information for a match, and initializes the
- static FMInfo structure with device-specific information. fm_status returns a
- non-zero value if the devices are known, which can be used as a quick check
- for functioning flash.
- Also, fm_status can optionally copy the FMInfo static structure to a structure
- designated by the caller. Diagnostics, for example, could display the pDevName
- string to confirm the type of devices installed.
-
-
- Erasing Sectors
-
-
- fm_sector_erase (the second routine in Listing 5) uses the Sector Erase
- feature of the devices to prepare them for being written with new data. Sector
- Erase erases all locations within a sector at once, which is where the term
- "flash" comes from. Conventional EEPROM technology erases each location
- separately. Any number of flash sectors can be erased concurrently.
- One curious thing about flash is that this "electrically erased" condition
- results in all bits being set to 1. A related curiosity is that 1 bits erase
- faster than 0 bits. (See [7] for a good explanation of how flash memory
- operates at the cell level.) The time required for erasure also varies with
- device temperature and the number of lifetime erase/write cycles performed.
- Figure 2 shows how erase and write times increase with use for an Am29F010.
- See also [8] for more information on erasure time.
- In anticipation of success -- where fm_error is not called -- fm_sector_erase
- and fm_write both clear the fm_err structure to all zeros. This action
- prevents uninitialized values from being left for the caller in fm_err. Note
- that fm_err cannot be initialized in its declaration, since embedded system
- compilers tend to place initialized data into ROM rather than RAM.
- The construction and use of the SectorAddrMask variable deserves mention. The
- code uses this variable to convert a pointer into a flash sector to a byte
- offset from the base of that sector. This conversion assumes that the base
- address of the flash (FBASE) in the system address space is aligned to an even
- multiple of the flash address range (FSIZE) -- i.e., FBASE mood FSIZE must be
- zero. This assumption is significant when supporting devices of various sizes.
- Prior to the first loop in fm_sector_erase, the devices are unlocked for
- erasure. The first loop completes the sector erasure command sequence by
- writing a secondary command code to an erase within each sector to be erased.
- The devices consider the erase command sequence complete if they receive no
- secondary command within a certain time period (e.g. 80 msec), so it may be
- necessary to disable interrupt service routines during this time.
- The second loop in fm_sector_erase waits for the erase operation to complete.
- The erase and write processes are controlled by an algorithm internal to the
- devices. When these internal algorithms are running, reading from the devices
- returns status information instead of ROM data. In particular, the high bit of
- the byte is inverted from the final, expected value. This operation guarantees
- that when the expected value occurs, the erase or write operation has
- completed successfully, which leads to a very simple polling loop for success.
- The erase is complete when the readback of any erased location returns
- FM_ERASE_VAL.
-
-
- Device Time-outs
-
-
- The fail-safe loop in fm_sector_erase ensures that malfunctioning hardware
- does not cause the firmware to hang. An earlier version of this routine
- assumed that completion or time-out were the only two possible outcomes.
- However, the specific tests used for completion and time-out ignore many other
- possible bit combinations which, though rare, should be tolerated by robust
- code.
- A "feature" of that earlier code version was the lack of an
- implementation-dependent timing constant like E_FS_ITER. Using such a constant
- may not be desirable, but I don't know of a way to handle all three cases
- (success, time-out, and other) without assuming another
- implementation-dependent resource, such as a watchdog timer.
- fm_sector_erase calls the fm_tofs_error routine (Listing 5) if the fail-safe
- loop expires. Its job is to determine whether or not a timeout occurred. This
- process sounds simple enough, but two things complicate the code here: timeout
- detection itself and parallel devices.
- Figure 3 illustrates the byte data returned by polling an erased location
- during an erase cycle. While the erase is in progress, bit 7 (DQ7) shows the
- inverse of the final expected data. If a location cannot be successfully
- erased, bit 5 (DQ5) will go high when the device gives up trying and times
- out. The data book warns that DQ5 and DQ7 may change at the same time. Thus, a
- single read showing DQ5 high cannot be trusted as indicating a timeout, since
- DQ7 may be transitioning to final valid data, which might coincidentally have
- DQ5 set.
- A further complication is that we are using two devices in parallel. The case
- where only one device times out must be properly handled. I will leave as an
- exercise for the reader to figure out the nuances of Boolean logic in the
- first three lines of code.
-
-
- Programming New Data
-
-
- Listing 6 shows the fm_cpy routine. fm_cpy writes new data into sectors which
- are assumed to have been erased. The data books refer to this operation as
- "programming" the data. (The parameter order purposely mimics memcpy.)
- Each byte to be written requires a separate unlock sequence. We applied heavy
- optimization techniques to this routine to minimize the effect of this
- overhead. Our hand-optimization (mentioned in the function header comment) is
- aware of the kind of assembly code that will ultimately be generated here. For
- example, using a short instead of an int for the fail-safe counter allowed the
- compiler to generate a faster looping construct. An 11 percent improvement in
- speed resulted from this one change. This routine is then both optimal for our
- specific implementation and portable.
- The bulk of this routine will look familiar from previous routines. For
- efficiency, we replicate a call to fm_cmd using register pointers and data. We
- then write the bytes to be programmed to the desired location. The completion
- polling loop and error handling mimics the code in fm_sector_erase.
- As indicated by a comment statement, two program lines occuring before the
- inner loop perform a kind of parallel processing. After we write new data to
- the current location we must wait for the write to complete. We can execute
- more program statements in the meantime. Analysis shows that these two lines
- execute well before the write completes. This results in fewer polling
- iterations, which speeds up the innermost loop.
-
-
- The High-Level Routine
-
-
- The fm_write routine (Listing 7) makes use of all the other routines in
- fmutl.c to accomplish its function: writing an arbitrary sized buffer of data
- to an arbitrary area of the flash.
- We chose to abstract to this level for a number of reasons. Writing new data
- to all of flash, our most common operation, requires a simple call:
- fm_write(FLASH_BASE, FLASH_BASE, pNewData, DataSize, 0);
- The complexities of sector sizes, erasing before writing, device
- identification, and so on are encapsulated.
- Because sectored flash devices can only erase entire sectors, writes that are
- not aligned to sector boundaries require special handling. Rather than
- implement this complexity in various separate locations, we chose to
- encapsulate it as well.
-
-
-
- Optional Scratch Buffer
-
-
- The encapsulation approaches presented thus far cannot hide one aspect of
- sectored flash memory which differs from normal RAM: It's impossible to change
- just one byte within a sector.
- Consider the case where a program calls fm_write to write a single word to a
- sector already containing healthy data. Erasing the sector and writing the new
- word leaves the remaining words in that sector changed to 0xFFFF -- not likely
- what the caller expected.
- To get around this problem, the pScrBuf parameter lets the caller pass a
- pointer to a buffer. If pScrBuf is not a NULL, uses it to save and restore any
- existing flash sector data not being reprogrammed.
- If pScrBuf is NULL, fm_write transfers the caller's new data directly to the
- flash sector. Non-buffered transfers accommodate the case in which the caller
- wishes to transfer a large block of data to flash memory and doesn't care what
- happens to the remaining (unwritten) portion of the sector. Giving the caller
- this control is a performance feature, since not using the buffer saves time.
- Allowing the caller to provide the buffer also avoids forcing fm_write to
- acquire an implementation-dependent resource (memory) at run time. The caller
- can use fm_status and the SectorSize field of the FMINFO structure to size
- this buffer.
-
-
- The fm_write algorithm
-
-
- The function header comments describe the external interface. The following
- pseudo-code provides an overview of how the routine works:
- fm_status();
- if (bad parameters)
- fm_error();
- if (not starting at a sector boundary)
- handle partial write to first sector;
- if (data doesn't end on a sector boundary)
- setup for partial last sector write;
- erase the remaining sectors;
- write the remaining user data;
- if (last sector was partial && pScrBuf)
- write pScrBuf data to last sector;
- The bulk of this routine handles the partial sector cases. If the caller only
- writes complete sectors, the extra complexity isn't used.
- The first major conditional, based on keep_before, handles the case where the
- beginning of the write doesn't start on an even sector boundary. In this case,
- the algorithm computes the following key length values:
- keep_before = number of bytes before user data
- wri_len = number of bytes of user data
- keep_after = number of bytes after user data
- These values reflect the start and end boundaries of the user data within the
- initial sector. If a scratch buffer was provided, the buffer is used to save
- the original contents of the sector. Next, the routine erases the sector and
- writes the new user data. Then, the original sector data before and/or after
- the new user data is restored, if a scratch buffer was provided.
- The next major conditional, based on wri_len, handles the case where the write
- does not end on an even sector boundary. The wri_len and keep_after variables
- are recomputed. If a buffer was provided it is loaded with the original
- contents of the last sector.
- Next, the routine erases the remaining sectors and writes them with the new
- user data. Finally, the routine writes the remainder of the final sector from
- the scratch buffer, if provided.
-
-
- Testing
-
-
- The code disk contains a third source module, flashtst.c, that we have used to
- unit test these routines. One routine performs a few basic memory tests
- (zeros, ones, unique data) suitable for a manufacturing check-out process.
- Another routine performs an exhaustive test of all combinations of writes on
- and off sector boundaries, which helps test the code integrity.
- This module can be compiled as a stand-alone utility or as an independently
- linkable module. While modularized, it does rely on external routines
- available in our embedded system for user input and output.
-
-
- The Tip of the Iceberg
-
-
- The code presented here works well for our purposes, but there are many extra
- features supported by the devices that you may want to use:
- Many flash devices support an erase suspend feature. Recall that during erase,
- data reads return status information rather than real data. With some hardware
- assistance an application could implement "background erasing." By using
- interrupt handlers to suspend and resume erasing, data could be read from
- sectors not being erased, which would allow the application to continue while
- the erase occured during idle bus cycles.
- Creative algorithms could also take advantage of the fact that flash writes
- can change 1-bits to 0-bits (but not vice-versa) without an erase cycle.
- "Wear leveling" is a technique commonly applied to heavy use flash
- implementations. You can maximize device lifetime with low-level remapping of
- the sectors as necessary to keep them all at roughly the same number of erase
- and write cycles. You can also automatically "retire" sectors as they become
- unusable.
- File systems are a typical next step beyond using flash as a better EPROM.
- There are various hardware and software solutions available today implementing
- this logical next step.
-
-
- Source Sources
-
-
- Source code for interfacing with flash devices is available. Intel provides
- assembly and C code in their flash data books (see [6]). AMD offers Embedded
- Development Kit Driver Software free for the asking or via their California
- BBS (contact your local rep).
-
-
- Conclusion
-
-
-
- Flash memory allows embedded systems to easily adapt to changing requirements.
- After working with flash memory devices for awhile now in a fast-paced
- development environment, I can't imagine going back to burning EPROMs. And the
- features flash provides to our customers make our products even more
- competitive.
- References
- [1] Arvind Rana. "Designing and Debugging with Flash ROMs," Proceedings of the
- Sixth Annual Embedded Systems Conference, Volume 2, September 1994, pp.
- 145-153.
- [2] Brian Dipert and Markus Levy, Designing With Flash Memory (Annabooks,
- 1994).
- [3] Advanced Micro Devices, Inc. 1994/1995 Flash Memory Products Data
- Book/Handbook.
- [4] Atmel Corporation 1994 Single Voltage Flash Memory Design & Application
- Book.
- [5] Intel 1994 Flash Memory data books Volumes I & II.
- [6] ibid., Vol. I, Chapters 3 and 4, Application Notes.
- [7] ibid., Vol. II, ppg 9-1 to 9-5, Engineering Report ER-20.
- [8] ibid., Vol. II, pg 9-11, Figure 5.
- Flavors of Flash
- Manufacturers like Intel, AMD, Atmel and others offer a variety of flash
- memory devices with a long list of features.
- Here is a partial list:
- 32 KB to 4 MB capacity per device (and growing)
- 1,000 to 100,000 minimum erase/write cycles
- 8 to 1,024 sectors
- 45 to 250 nsec access time
- same size sectors versus "Boot-block" sizes
- byte versus word accessing (some pin-selectable)
- pin-out upgradable to larger capacity (some models)
- JEDEC pin-out and command compatible (some models)
- 3.3 versus 5.0 volt (some pin-selectable)
- 12 volts erase/write voltage (some models)
- power-down modes (some models)
- on-board RAM buffering for writes (some models)
- writes during erase (some models)
- PCMCIA flash memory cards (e.g. Intel's 40 MB Memory Card) utilize an array of
- flash chip dies within a single package.
- Figure 1 Two flash devices wired in parallel
- Figure 2 Erase and write times vs. device cycles
- Figure 3 Simple timing diagram of four signals over time
-
- Listing 1 Data structures used by flash memory programmer
- /* fmutl.h - flash memory utility module header */
-
- #ifndef FMUTL_H_____LINEEND____
- #define FMUTL_H_____LINEEND____
-
- /* macro returning number of elements in an array: */
- #define ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0]))
-
- /***********************************************************************
- The fm_status() routine can return the following information about flash
- memory devices. NOTE: fields ending with '_' are intended for internal
- use by fmutl.c only:
- ***********************************************************************/
-
- typedef struct { /** Flash Memory information structure: **/
- /* private: */
- unsigned short MfrID_; /* (manufacturer ID code) */
- unsigned short DevID_; /* (device ID code) */
- short MaxErase_; /* (max erase time in seconds) */
- short MaxWrite_; /* (max byte-write time in ms) */
- int NumSect_; /* (number of sectors per chip) */
- /* public: */
- size_t SectorSize; /* bytes per sector (both chips) */
- size_t TotalSize; /* total number of bytes (both chips) */
- int NumProt; /* >= O: # of protected sectors;
-
- -1: prot. sectors are non-contiguous
- -2: prot. mismatched b/w devices */
- int FirstProt; /* first protected sector, ordinal 0 */
- char *pDevName; /* ptr to manufacturer & device name */
- } FMINFO;
-
- /***********************************************************************
- If the return value of a routine in fmutl.c indicates failure, the fm_err
- global structure will contain the following additional information about
- the error.
- ***********************************************************************/
-
- typedef struct { /** Flash routine error information structure: **/
- unsigned short *addr; /* flash memory address of problem */
- unsigned short exp; /* value expected */
- unsigned short act; /* actual value read */
- unsigned char code; /* error code byte (see fmutl.c) */
- char *pMsg; /* ptr to error message string */
- } FMERR;
-
- extern FMERR fm_err; /* defined in fmutl.c */
-
- /**********************************************************************
- Prototypes for the global routines in fmutl.c:
- ***********************************************************************/
-
- #ifdef__STDC______LINEEND____
-
- extern fm_status(unsigned short *pBase, FMINFO *pFMIfo);
-
- extern fm_write(unsigned short *pBase, unsigned short *pDst,
- unsigned short *pSrc, size_t length,
- unsigned short *pScrBuf);
-
- extern void fm_error(unsigned short *addr, unsigned short exp,
- unsigned short act, int errcode);
-
- #endif
-
- #endif /* FMUTL_H_ */
-
- /* End of File */
-
-
- Listing 2 Constants and Statics
- #include <string.h>
- #include "fmutl.h"
-
- /** all these magic numbers are from the AMD spec sheets: **/
-
- /* address and data for command writes: */
- #define FM_ADDR1 0x5555 /* these are for the first "magic */
- #define FM_DATA1 0xAAAA /* write" of a command preamble */
- #define FM_ADDR2 0x2AAA /* these are for the second "magic */
- #define FM_DATA2 0x5555 /* write" of a command preamble */
-
- /* command codes: */
- #define FMCMD_RESET 0xF0F0 /* "Read/Reset Command" */
- #define FMCMD_AUTOSEL 0x9090 /* "Autoselect Command" */
-
- #define FMCMD_WRITE 0xA0A0 /* "Byte Program Command" */
- #define FMCMD_ERASE 0x8080 /* "Chip or Sector Erase Command" */
-
- /* use this to skip last step of a command preamble [see fm_cmd()]: */
- #define FMCMD_NONE 0x0000 /* no command */
-
- /* secondary command codes for erase commands: */
- #define FMCMD2_SECTOR 0x3030 /* "Sector Erase" */
- #define FMCMD2_CHIP 0x1010 /* "Chip Erase" */
-
- /* miscellaneous other flash memory constants: */
- #define FM_PROT_SECT_MASK 0x0101 /* protected sector indicator bit(s) */
- #define FM_ERASE_VAL 0xFFFF /* value of an erased location */
- #define FM_TIMEOUT 0x2020 /* DQ5 - goes hi if device times out */
- #define FM_NDATA_POLL 0x8080 /* DQ7 - inverse of final data */
-
- /* special address offsets for Autoselect command reads: */
- #define FM_AO_MANUF 0 /* for manufacturer code readback */
- #define FM_AO_DEVID 1 /* for device ID readback */
- #define FM_AO_PROT_SECT 2 /* for protected sector readbacks */
-
- FMERR fm_err; /* global Flash error structure */
-
- static char *ErrMsgs[] = [ /** fm_err.code and fm_err.pMsg values: **/
- "x?? Unknown err", /* 0:. unused */
- "Bad devID codes", /* 1: unknown/mismatched/missing/bad chips */
- "Erase fail-safe". /* 2: sector erase fail-safe triggered */
- "Erase timed out", /* 3: sector erase command timed out */
- "Write fail-safe", /* 4: byte write fail-safe triggered */
- "Write timed out", /* 5: byte write programming timed out */
- "fm_write params", /* 6: address or length parameter was odd */
- "fm_write overfl", /* 7: attempt to write past end of flash */
- }; /** see also ErrMsg[] in fm_error()**/
-
- static FMINFO DevTable[] = { /* known device data table: */
- /* MfrID DevID EFS WFS NS SectSiz*2 TotSize*2 NP FP DevNameString */
- 0x0101, 0x2020, 20, 90, 8, 0x04000*2, 0x20000*2, 0, 0, "AMD Am29F010",
- 0x0101, 0xA4A4, 40, 90, 8, 0x10000*2, 0x80000*'2, 0, O, "AMD Am29F04*",
- };
-
- /* the routines in this module need to share this data: */
- static unsigned volatile short *pBase; /* ptr to the base address of Flash */
- static FMINFO FMInfo; /* info specific to these chips */
-
- /* End of File */
-
-
- Listing 3 Low-level routines
- /****************************************************************
- Set values in the global error structure.
- ****************************************************************/
-
- void fm_error(addr, exp, act, errcode)
- unsigned short *addr, exp, act;
- int errcode; /* only the low byte is significant */
- {
- static char ErrMsg[20];
- static char Hex[] = "0123456789ABCDEF";
-
-
- /* fill in the easy stuff: */
- fm_err.addr = addr;
- fm_err.exp = exp;
- fm_err.act = act;
- fm_err.code = (unsigned char) errcode;
-
- /* either use a built-in message, or construct one using errcode: */
- if (1 <= errcode && errcode < ARRAY_LEN(ErrMsgs))
- fm_err.pMsg = ErrMsgs[errcode];
- else
- { strncpy(ErrMsg, ErrMsgs[O], sizeof(ErrMsg));
- ErrMsg[1] = Hex[fm_err.code / 16];
- ErrMsg[2] = Hex[fm_err.code % 16];
- fm_err.pMsg: ErrMsg;
- }
-
- }
-
- /**********************************************************************
- Send unlock commands to the chips. This magic permutation of writes takes
- the chips out of normal read-only mode, and puts them into the indicated
- command mode (see the FMCMD_constants). This code is replicated in the
- fm_cpy() routine for performance reasons.
- ***********************************************************************/
-
- static void fm_cmd(cmd)
- unsigned short cmd; /* command code = an FMCMD_ constant */
- {
- /* write first magic value to first magic location: */
- *(pBase + FM_ADDR1) = FM_DATA1;
-
- /* write second magic value to second magic location: */
- *(pBase + FM_ADDR2) = FM_DATA2;
-
- /* if desired, write a command code to the final magic location: */
- if (cmd != FMCMD_NONE)
- *(pBase + FM_ADDR1) = cmd:
- }
-
- /***********************************************************************
- Reset the devices. Two resets are performed because the first may not
- be accepted if the devices were left in a strange state (e.g. if a prior
- command sequence was interrupted before completion). The second is sure
- to be heard by sane devices.
- ***********************************************************************/
-
- static void fm_reset()
- {
- fm_cmd(FMCMD_RESET);
- fm_cmd(FMCMD_RESET):
- }
- /* End of File */
-
-
- Listing 4 Status Routines
- /***********************************************************************
- Provides information about protected sectors in the chips. FMInfo and pBase
- are assumed to be initialized. Leaves the chips in AutoSelect mode.
- ***********************************************************************/
-
-
- static void fm_protected(pFirstProt, pNumProt)
- int *pFirstProt, *pNumProt; /* see the FMINFO struct for details */
-
- {
- unsigned volatile short *ptr; /* ptr into flash space */
- unsigned short val; /* value read back */
- int sect; /* iterates thru the sectors */
-
- /* init our pointer to the magic offset in the last sector: */
- ptr = pBase + ((FMInfo. TotalSize >> 1) + FM_AO_PROT_SECT);
- ptr -= FMInfo.SectorSize >> 1;
-
- /* command the chip to read back the protected sector data: */
- fm_cmd(FMCMD_AUTOSEL);
-
- /* initialize these to mean "no protected sectors found": */
- *pNumProt = 0;
- *pFirstProt = -1;
-
- /* loop checking each sector: */
- for (sect = FMInfo.NumSect; --sect >= 0; )
- {
-
- /* bit set indicates sector is protected: */
- val = *ptr & FM_PROT_SECT_MASK;
- if (val)
- { /* if both devices don't agree, error out: */
- if (val!= FM_PROT_SECT_MASK)
- { *pNumProt = -2;
- return;
-
- }
-
- /* if protection is non-continuous error out: */
-
- if (*pFirstProt != -1 && *pFirstProt != sect + 1)
- { *pNumProt = -1;
- return;
- }
-
- /* set index to this sector and bump the count: */
- *pFirstProt = sect;
- ++*pNumProt;
- }
-
- /* bump pointer to the magic location in the next sector: */
- ptr -= FMInfo.SectorSize >> 1;
- }
- }
-
- /*********************************************************************
- Resets and returns data about the flash chips. Fills in our local
- FMInfo structure, and optionally fills in an FMINFO structure for the
- caller. Leaves the chip(s) in the normal ROM state. Zero is returned
- on error and fm_err contains failure data.
- *********************************************************************/
-
- fm_status(pbase, pFMInfo)
-
- unsigned short *pbase; /* must point to the *base* of the Flash memory */
- FMINFO *pFMInfo; /* may be NULL */
- {
- unsigned short mfr, dev; /* IDs read back from devices */
- FMINFO *pDev; /* traverses the DevTable[] */
-
- /* share this with other routines in this module: */
- pBase = pbase;
-
- /* clear out error structure: */
- memset(&fm_err, 0, sizeof(fm_err));
-
- /* reset the chip: */
-
- fm_reset();
-
- /* send down command to read back the embedded data: */
- fm_cmd(FMCMD_AUTOSEL);
-
- /* read back the codes: */
- mfr = *(pBase + FM_AO_MANUF);
- dev = *(pBase + FM_AO_DEVID);
- /* check for known devices: */
-
- pDev = DevTable + ARRAY_LEN(DevTable);
- while (--pDev >= DevTable)
- if (pDev->MfrID_== mfr && pDev->DevID_ == dev)
- { memcpy(&FMInfo, pDev, sizeof(FMInfo));
- break;
- }
-
- /* if not found in our table, return error: */
- if (pDev < DevTable)
- { /* (could also be mismatched, missing, or fried devices) */
- fm_reset();
- fm_error(pBase, mfr, dev, 1);
- return 0;
- }
-
- /* go get & fill in protected sector information: */
- fm_protected(&FMInfo.FirstProt, &FMInfo.NumProt);
-
- /* fill in structure for caller if they want us to: */
- if (pFMInfo)
- memcpy(pFMInfo, &FMInfo, sizeof(FMINFO));
-
- /* exit readback mode and we're done: */
- fm_reset():
- return 1;
- }
- /* End of File */
-
-
- Listing 5 Time-out and erase routines
- /***********************************************************************
- Handle a time-out or fail-safe error. The expected value is checked with
- the actual value to determine if a time-out happened. fm_error is called
- with errcode for a fail-safe error, or with errcode+1 for a time-out error.
- ***********************************************************************/
-
-
- static void fm_tofs_error(addr, exp, act, errcode)
- unsigned short *addr, exp, act;
- int errcode; /* fail-safe error=errcode; time-not error=errcode+1 */
- {
- unsigned short to_val; /* value used to check for time-out */
- unsigned short tmp; /* time-out check temporary */
-
- /* this value has timeout & data-poll bits set to mean time-out: */
- to_val = (~exp & FM_NDATA_POLL) FM_TIMEOUT;
-
- /* mask out the interesting bits and xor them: */
- tmp = (act & (FM_TIMEOUT FM_NDATA_POLL)) ^ to_val;
-
- /* both bits clear in a byte means that chip timed-out: */
- if (!(tmp & 0x00ff) !(tmp & 0xff00))
- ++errcode;
-
- /* timeout error or fail-safe error handling: */
- fm_error(addr, exp, act, errcode);
-
- /* leave chips usable */
- fm_reset();
- }
-
- /*********************************************************************
- Erase by sectors. The sector(s) which are overlapped by the indicated
- flash address memory range are erased. Returns non-zero on success;
- otherwise zero is returned and fm_err contains failure data. Assumes
- FMInfo and pBase are initialized.
- *********************************************************************/
-
- #define E_FS_ITER 240000 /* number of fail-safe loop iterations per sec. */
- /** Re-measure this constant if the fail-safe loop is changed! **/
-
- static fm_sector_erase(pLow, pHigh)
- volatile unsigned short *pLow; /* ptr somewhere into first sector to erase */
- volatile unsigned short *pHigh; /* ptr somewhere into last sector to erase */
- {
- size_t SectorAddrMask; /* converts ptr to offset within a sector */
- size_t SectOffset; /* offset into ending sector */
- unsigned long fail_safe; /* safety counter */
-
- /* this masks off address bits indicating offsets into a sector: */
- SectorAddrMask = FMInfo.SectorSize - 1;
-
- /* set end pointer to the last location in it's sector: */
- SectOffset = ((size_t) pHigh) & SectorAddrMask;
- pHigh = pHigh - (SectOffset >> 1) + (FMInfo.SectorSize >> 1) - 1;
-
- /* send erase preamble sequence: */
- fm_cmd(FMCMD_ERASE);
-
- /* send sector erase preamble sequence: */
- fm_cmd(FMCMD_NONE);
-
- /* loop indicating which sectors to erase: */
- while (pLow <= pHigh)
- {
-
- /* tell chip to erase this sector: */
- *pLow = FMCMD2_SECTOR;
-
- /* bump pointer by one sector: */
- plow += FMInfo.SectorSize >> 1;
- }
-
- /* loop checking for erase completion: */
- for (fail_safe = FMInfo.MaxErase_* E_FS_ITER; --fail_safe; )
- if (*pHigh == FM_ERASE_VAL)
- return 1; /* done! */
-
- /* time-out and fail-safe error handling: */
- fm_tofs_error(pHigh, FM_ERASE_VAL, *pHigh, 2);
- return 0;
- }
-
- /* End of File */
-
- Listing 6 Data copy routine
- /***********************************************************************
- Internal routine to copy data to Flash Memory. It is assumed that the
- destination area has been erased, and that bytes is an even number.
- Returns non-zero on success; zero is returned on error and fm_err contains
- failure data. FMInfo and pBase are assumed initialized.
- * * * NOTE: This routine has been hand optimized for performance. * * *
- ***********************************************************************/
-
- #define W_FS_ITER 350 /* number of fail-safe loop iterations per msec */
- /** Re-measure this constant if the fail-safe loop is changed! **/
-
- static fm_cpy(pDst, pSrc, bytes)
- register volatile unsigned short *pDst; /* where in Flash to copy data to */
- register unsigned short *pSrc; /* where to copy data from */
- register size_t bytes; /* # bytes to copy -- must be even */
- {
- register volatile unsigned short *ptr1; /* fast FM_ADDR1 ptr */
- register volatile unsigned short *ptr2; /* fast FM_ADDR2 ptr */
- register unsigned short fmd1, fmd2; /* fast FM_DATA values */
- register unsigned short val; /* value read in verify loop */
- register unsigned short fail_safe; /* safety counter */
-
- /* put these in registers for speed: */
- ptr1 = pBase + FM_ADDR1;
- ptr2 = pBase + FM_ADDR2;
- fmd1 = FM_DATA1;
- fmd2 = FM_DATA2;
-
- /* loop writing and verifying a word at a time: */
- while (bytes)
- {
- /* this duplicates a call to fm_cmd(), for speed: */
- *ptr1 = fmd1;
- *ptr2 = fmd2;
- *ptr1 = FMCMD_WRITE;
-
- /* write two bytes: */
- *pDst = *pSrc;
- /* do this here - a bit of parallel processing: */
-
- bytes -= 2;
- val = *pSrc;
-
- /* loop checking for write completion: */
- for (fail_safe = FMInfo.MaxWrite_ * W_FS_ITER; --fail_safe; )
- if (*pDst == val)
- break; /* done! */
-
- /* if loop was exhausted, go handle the error: */
- if (fail_safe == 0)
- { fm_tofs_error(pDst, val, *pDst, 4);
- return 0;
- }
-
- /* move to the next location and loop back: */
- ++pSrc;
- ++pDst;
- }
-
- /* return happy: */
- return 1;
- }
- /* End of File */
-
- Listing 7 Main programming routine
- /**********************************************************************
- Write data to the Flash Memory. Returns non-zero on success; on error
- zero is returned and fm_err contains failure data. The pDst and length
- parameters should be word aligned.
-
- The pScrBuf parameter determines the fate of words erased in a sector but
- not written with new data:
- if (pScrBuf == NULL), then writes to partial sectors leave the
- unwritten words of those sectors erased & unwritten.
- if (pScrBuf! = NULL), then *pScrBuf is assumed to point to at least
- FMINFO.SectorSize bytes, and unwritten (but erased) words
- in partially written sectors will be saved (in the scratch
- buffer) and re-written, maintaining their prior values.
- **********************************************************************/
-
- fm_write(pbase, pDst, pSrc, length, pScrBuf)
- unsigned short *pbase; /* ptr to the base of the Flash memory */
- unsigned short *pDst; /* where to write in Flash Memory space */
- unsigned short *pSrc; /* bytes from here are copied to Flash */
- size_t length; /* copy this many bytes from pSrc to pDst */
- unsigned short *pScrBuf; /* scratch buffer for partial sector writes */
- {
- size_t wri_len; /* # partial sector bytes of user data */
- size_t keep_before; /* # partial sector bytes prior to user data */
- size_t keep_after; /* # partial sector bytes after user data */
- size_t offset; /* word offsets for various uses */
- size_t SectorAddrMask; /* converts ptr to offset within a sector */
- unsigned short *pSector; /* ptr to even sector boundary */
- char errcode; /* non-zero indicates a parameter error */
-
- /* share this with other routines in this module: */
- pBase = pbase;
-
- /* clear out error structure: */
-
- memset(&fm_err, 0, sizeof(fm_err));
-
- /* reset the chip and load FMInfo with info about it: */
- if (!fm_status(pBase, (FMINFO *) 0))
- return 0;
-
- /* check input parameters for word alignment and overflow: */
- errcode = 0;
- if (((size_t) pDst & 1) (length & 1))
- errcode = 6;
-
- else if (pDst + (length >> 1) pBase + (FMInfo.TotalSize>> 1))
- errcode = 7;
-
- /* these errors return length in pieces in the exp and act fields: */
- if (errcode)
- { fm_error(pDst, (unsigned short) length >> 8,
- (unsigned short) length & 0xFFFF, errcode);
- return 0;
- }
-
- /* this masks off address bits indicating offsets into a sector: */
- SectorAddrMask = FMInfo.SectorSize - 1;
-
- /* see if we are starting on an even sector boundary: */
- keep_before = ((size_t) pDst) & SectorAddrMask;
-
- /* if the first sector is a partial, do it separately: */
- if (keep_before)
- {
- /* get a pointer to the start of the sector: */
- pSector = pDst - (keep_before)) 1);
-
- /* compute # bytes in this sector being written by user: */
-
- wri_len = FMInfo.SectorSize - keep_before;
- if (length < wri_len)
- wri_len = length;
-
- /* compute # bytes to keep after area being written: */
- keep_after = FMInfo.SectorSize - keep_before - wri_len;
-
- /* read existing Flash sector into scratch buffer: */
- if (pScrBuf)
- memcpy(pScrBuf, pSector, FMInfo.SectorSize);
-
- /* erase the sector: */
- if (!fm_sector_erase(pDst, pDst))
- return 0;
-
- /* copy the user's data to Flash: */
- if (!fm_cpy(pDst, pSrc, wri_len))
- return 0;
-
- /* restore data in sector before user's data: */
-
- if (pScrBuf)
- if (!fm_cpy(pSector, pScrBuf, keep_before))
- return 0;
-
-
- /* restore data in sector after user's data: */
- if (pScrBuf && keep_after)
- { offset = (wri_len + keep_before) >> 1;
- if (!fm_cpy(pSector + offset,
- pScrBuf + offset, keep_after))
- return 0;
- }
-
- /* bump pointers: */
- pDst += (wri_len >> 1);
- pSrc += (wri_len >> 1);
- length -= wri_len;
-
- /* quit if done: */
- if (!length)
- return 1;
- }
-
- /* see if we are ending on an even sector boundary: */
- wri_len = length & SectorAddrMask;
-
- /* handle partial final sector: */
- if (wri_len)
- {
- /* get a pointer to the start of the final sector: */
- pSector = pDst + ((length - wri_len) >> 1);
-
- /* compute # bytes to keep after area being written: */
- keep_after = FMInfo.SectorSize - wri_len;
-
- /* read partial existing Flash sector into scratch buffer: */
- if (pScrBuf)
- memcpy(pScrBuf, pSector + (wri_len >> 1), keep_after);
- }
- /* erase the remaining sector(s): */
-
- if (!fm_sector_erase(pDst, pDst + (length >> 1) - 1))
- return 0;
-
- /* copy the user's data to Flash: */
- if (!fm_cpy(pDst, pSrc, length))
- return 0;
-
- /* restore data in sector after user's data: */
- if (wri_len && pScrBuf && keep_after)
- { offset = wri_len >> 1;
- if (!fm_cpy(pSector + offset, pScrBuf, keep_after))
- return 0;
- }
-
- /* return happy: */
- return 1;
- }
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Using Associative Arrays
-
-
- Michael McClung
-
-
- Michael McClung is a senior staff engineer at Intecom Inc. He worked as a
- software contractor for 8 years before reentering the corporate work force. He
- received his Master of Science in Computer Science from the University of
- California at Lawrence Livermore Laboratory. His main interests include
- real-time systems, software development processes, and object-oriented
- programming. He can be reached at 214-447-8060 or mm@intecom.com.
-
-
-
-
- Introduction
-
-
- Associative arrays are well known in the software community. They are built
- into some languages such as AWK, Perl, and Smalltalk (as the data dictionary),
- and it is not unusual to find them in C++ class libraries since the language
- supports them well. You can easily find references to them by browsing through
- programming books. Yet, in my experience, associative arrays are not commonly
- used in C programming environments. This article defines associative arrays,
- shows where they can be useful, and presents an implementation in C.
-
-
- Defining Associative Arrays
-
-
- Interpretations may vary, but in this context the term associative array
- refers to an array that takes a user-defined type as an array index. Viewing
- all arrays as mapping functions, a conventional array is a function that maps
- only from an integer index to another type. For example
- a[5]= 19.58 maps the integer 5 to the floating point number 19.58.
- b[7]= "twelve" maps the integer 7 to the string "twelve".
- c[9]='D' maps the integer 9 to the char 'D'.
- Associative arrays remove the restriction of integer indexes, allowing
- arbitrary data types to directly map to other types, as in the following:
- a["first"] = 19.59 maps the string "first" to the floating point number 19.58.
- b[1.21]="twelve" maps the floating point number 1.21 to the string "twelve".
- c[employee_rec]=&d_rec maps the structure employee_rec to the record pointer
- &d_rec.
- The sparse array exemplifies another type of mapping problem that lends itself
- well to associative arrays. Suppose several points must be identified in a
- 10,000-meter cube. The simplest model is a three-dimensional array with each
- dimension ranging from zero to 10,000. Allocation of a conventional linear
- array far exceeds available memory. With an associative array, which does not
- depend on linear storage, the memory requirements are much smaller. The
- following code, using the associative array CUBEPOINT, requires only the
- locations used, one for each assignment.
- CUBEPOINT(1,123,1564)=TRUE;
- CUBEPOINT(100,256,5647)=TRUE;
- CUBEPOINT(1000,6123,9156)=TRUE;
- Associative arrays are an abstraction, not a particular data structure or
- algorithm, since many different implementations are possible.
-
-
- Design Requirements
-
-
- Since there are lots of ways to implement associative arrays, I've established
- some criteria to narrow down the choices. In this section I describe these
- criteria, which are efficiency, a generic implementation, and an intuitive
- interface.
-
-
- An Efficient Algorithm
-
-
- The algorithm must be efficient in both execution speed and memory usage. Two
- primary criteria are efficient insertions of new elements and fast searches
- for existing elements. In addition, I want neither the amount of storable data
- nor the form to be strictly limited. Two excellent algorithms in terms of
- efficiency are AVL trees and hashing. AVL trees, which maintain a balanced
- search tree, provide the best memory handling characteristics. Hashing, which
- usually involves a simpler algorithm, provides a search speed hard to match.
- In this article, I present a design that uses hashing, mainly because its ease
- of implementation makes the example code more clear.
-
-
- A Generic Implementation
-
-
- A practical implementation must be generic in the sense that a single module
- must handle arbitrary user-defined types and array sizes. I accomplish this in
- two ways. First, the definition of each array is self-contained in an array
- identifier. This encapsulation of the complete definition by an identifier
- allows independent arrays to coexist. Second, I categorize the index for each
- array definition as either a string type or raw binary type. Knowing how to
- read, compare, and copy only these two types, I can implement a generic hash
- algorithm. The user specifies a key's type (string or binary) when the array
- is created.
-
-
- An Intuitive Interface
-
-
- Associative array access should resemble conventional array access as much as
- possible to ease use and improve readability. The lack of language support for
- generic functions and operator overloading exacerbates the problem of creating
- a good interface. In part, I sidestep the interface requirement by pushing
- some of the responsibility onto the user. Wrapping the access function with a
- user macro provides a respectable, albeit inelegant, associative array
- interface.
-
- Ideally, an associative array of file descriptor pointers, each indexed by
- file name, would be accessed as follows:
- fd["filexy"]=filedesc;
- Because C does not support this extension, the actual interface is more
- formidable:
- *((FILE **)aa_addr(fd,"filexy")=filedesc;
- However, by adding a macro wrapper, users can approximate the look of
- conventional arrays:
- #define FD(n) (*((FILE **)aa_addr(fd.n))
- . . . .
- FD("filexy")=filedesc;
- Since the compiler's preprocessor does not allow dynamic macro generation, the
- user must define an access macro for each array definition. I explain these
- macros further in the examples.
-
-
- The Code
-
-
- Two source files contain the associative array code. Listing 1 shows the
- header file that must be included by the application. This file defines the
- associative array structure, declares each entry point, and provides macro
- definitions. The second file contains the module's source, which must be
- compiled and linked with the application. Throughout the description of the
- source, I use the term key to refer to the array index.
- Listing 2 through Listing 7 break the module's source into functional parts,
- each separately explained below. For publication I have kept in-line
- documentation to a minimum, but the accompanying text should clarify the
- purpose of each code section.
-
-
- Part 1: The Module Header
-
-
- Listing 2 shows the module's header file, which defines macros and makes
- declarations for the rest of the module.
-
-
- Part 2: Creating the Array
-
-
- The aa_create function in Listing 3 creates an associative array. Creating an
- array entails allocating a structure (aa), loading the structure with all
- values describing the array characteristics, and returning the structure
- pointer. The calling function must save the structure pointer as an identifier
- for all other operations on the array. The caller should treat the identifier
- as an abstract data type, never directly accessing the structure. If an error
- occurs during creation, aa_create returns a null identifier.
- The identifier structure, AA, contains eight fields that make up a complete
- definition. type indicates whether the key is a string type or binary type.
- (This distinction is required since string operations, like compare and copy,
- are different from binary memory operations.) key_size gives the key's size in
- bytes. The key_size parameter is meaningful only with binary keys since string
- sizes are automatically determined with the strlen function. data_size
- specifies the maximum size in bytes of each array element. Two internal tables
- of pointers, keys and data, point respectively to a key and its corresponding
- data element; these two tables maintain the actual contents of the associative
- array. max_elements indicates the size of the keys and data tables, the
- maximum number of elements the array can hold, barring memory limitations. The
- last field, hash_function, points to the hash function that operates on the
- key to produce the hash value. This field, usually not user-defined, is
- discussed further in Part 4.
- Shown at the end of Listing 3 is the prime_size function used by aa_create and
- later by aa_resize. This function forces the array size to be prime to improve
- the efficiency of the hash algorithm (see Part 4).
-
-
- Part 3: Accessing the Array
-
-
- Once an associative array has been created with aa_create, programs can index
- it with the aa_addr function in Listing 4. aa_addr takes two parameters: an
- associative array identifier and a key. The key indexes the associative array
- to find the corresponding data element. When the data element is found,
- aa_addr returns its address to the calling function, which can directly store
- into or retrieve from the data area.
- Since aa_addr adds new keys to the associative array, it also monitors the
- amount of free space left. If the array becomes too full -- 75 percent in the
- current implementation -- this function dynamically expands the size by 50
- percent. Maintaining a sufficient number of empty slots in the array's hash
- tables insures an efficient search. I chose 75 percent as the critical number
- since studies show a rapid degradation in efficiency as the table load
- approaches 80 percent [1]. Other hash algorithms have better behavior when
- approaching capacity, but at a tradeoff of other factors, such as complexity.
- Part 6 describes the function that changes the array size.
- After checking the array size, aa_addr calls hashindex to find the index
- corresponding to the specified key. Once found, the same index offsets into
- both the keys table and data table. If the keys table already contains the
- sought after key, aa_addr returns the corresponding data address, completing
- the function. If the key is not in the table, aa_addr must allocate memory,
- save the key, and then return the data address. Saving the key guarantees the
- same data address is returned for future accesses with identical keys.
-
-
- Part 4: Hashing the Keys
-
-
- The whole module pivots on the hashindex function shown in the second part of
- Listing 5. This function, along with the subordinate function, hash_function,
- implements the hash algorithm. This particular hash implementation is called
- Double Hashing with Linear Probe. (See the sidebar "Common Hashing Techniques"
- for an overview of hashing techniques.)
- hashindex calls the hash function for the starting search location. hashindex
- then continues its search until it finds either a match for the input key or
- an empty slot. (An empty slot indicates the key is not in the table.) When the
- hashindex makes comparisons for matching keys it must consider whether a
- binary or string key is used (i.e., whether to use memcmp or strcmp). If the
- search was not successful on the first attempt, hashindex makes a second call
- to the hash function using a different size parameter to give the effect of a
- different hash calculation. If its search is still not successful, hashindex
- continues with a linear search until it finds either a matching key or an
- empty slot. On success, hashindex returns the index of the matching or empty
- slot.
- A good hash function is critical to the efficiency of the hash algorithm.
- Unless the caller defines a custom hash function when the array is created,
- the program will use hash_function by default. The user may wish to define a
- custom hash function for one of two reasons. The first reason is for speed,
- since the function can be optimized for the specific key type. The second and
- most critical reason is to handle non-continuous keys. A non-continous key is
- a key that contains sections of non-data bytes.
- An example of a non-continuous key is a record structure with multiple
- character strings; the data after each string's terminator is undefined,
- leaving gaps in the key. A more subtle example is a complex key type that the
- compiler breaks up to fit on word boundaries. This behavior is compiler
- dependent and should be checked if in doubt. (Dumping memory with the debugger
- is an easy way.) A non-continuous key can corrupt the calculation of the hash
- value by introducting random data. If the key is not guaranteed to be
- continuous, the caller must either provide a custom hash function or
- initialize the key before use. A custom function can bypass any compiler
- dependencies since it knows the structure of the key. Setting the complete key
- to zeros (as with memset(key, 0, sizeof(key)) also resolves the problem by
- initializing any memory gaps to a consistent value.
- hash_function works well on both binary and string keys. The implementation
- sacrifices some clarity for the sake of efficiency since this function more
- than any other affects the speed of the module.
- hash_function builds a shift table the first time it is invoked. The shift
- table helps guarantee that similar keys, such as "x-coordinate" and
- "y-coordinate," do not produce similar hash values. hash_function uses static
- table initialization since the size must be large enough to eliminate error
- checking in the hash calculation.
- Two for loops calculate the hash value, the first for string keys and second
- for binary keys. Each loop takes the exclusive-OR of all bytes in the key,
- with each byte first being modified by a shift value. Only bytes up to the
- null terminator are used for string keys. The aim of both loops is to minimize
- machine cycles while getting a good hash number.
- The last and essential part of creating the hash value is performing division
- with the modulo operator. Division completes the randomization of the hash
- number and bounds it to the size of the hash table. This is where a prime
- table size pays off; taking the modulo of a prime number gives a much better
- hash distribution.
-
-
- Part 5: Retrieving the Keys
-
-
- aa_keys, in Listing 6, returns all keys in an associative array. Because
- associative array indexes are not ordered, this function must provide a means
- of sequentially visiting every element in the array. The array identifier is
- the input parameter for aa_keys; it outputs an array of key pointers and the
- array's size. The output array is allocated dynamically so it must be freed by
- the caller, but individual key pointers should be considered constants since
- they point into the hash table. The keys are gathered by walking through every
- entry in the keys table. If the entry contains a valid key, neither null nor
- deleted, its pointer is saved in the output array.
-
-
-
- Part 6: Resizing the Array
-
-
- aa_resize, shown in Listing 7, expands or shrinks the size of the associative
- array. aa_resize is a module entry point and is also called internally from
- aa_addr when the array becomes too full. My original implementation of this
- module did not allow the array size to be changed, but I found myself wasting
- memory for worst-case allocations. The user still has the option of avoiding
- the resize overhead by allocating a large enough array at creation time.
- No short-cut exists to change the size of a hash table since the size is an
- integral part of the hash function. When a hash table is resized, aa_resize
- must allocate complete new tables and rehash every valid key in the old table
- to a new index location. aa_resize moves the key and associated data pointers
- to the new locations in the new tables. If the new tables cannot be allocated
- or the requested size is too small, the function fails.
-
-
- Part 7: Deleting and Testing Elements
-
-
- Listing 8 contains two functions: aa_delete removes an element from the array,
- and aa_defined tests if a key is already in the array.
- aa_delete takes two parameters, the array definition and the key to be
- deleted. The deletion frees both the key and the data memory. A flag,
- DELETED_KEY, is stored in the key pointer to prevent the slot from being
- reused. The pointer cannot appear empty since hashindex assumes the search is
- complete when an empty slot is found. If multiple keys hashed to the same
- index as the deleted key, those keys stored after the one deleted would never
- be reached since an empty slot would be found first. This aspect of key
- deletion is inherent to the hashing algorithm used.
- aa_defined returns a true value if the specified key is already stored in the
- array. Users should call aa_defined to find out if the key they're looking for
- is in the table. The application should never examine the data returned from
- aa_addr to determine if a key is contained in the array, since aa_addr always
- adds new keys to the array.
-
-
- Examples
-
-
- Listing 9 and Listing 10 show two complete programs. These programs link with
- the associative array module and standard libraries, but are otherwise self
- contained. The first program counts the frequency of words read from standard
- input and prints a frequency list. The second program shows an associative
- array implementation of a sparse array.
- The user-defined macros, WORD_COUNT and SPARSE, simplify access to the arrays.
- A simple rule to follow when creating these macros is to cast the return value
- of aa_addr to a pointer to the array's data type and encase this expression
- with the dereference operator. Dereferencing the returned address allows the
- macros to be transparently used with nearly all operators. The user may
- optionally use the macro, AA_ACCESS, to create the access macro. AA_ACCESS is
- defined for this purpose in the header file.
-
-
- Conclusion
-
-
- Associative arrays are a general purpose abstraction fitting a wide range of
- problems. Effectively incorporating higher abstractions into our programming
- solutions provides a path to lower cost and complexity. A few examples have
- shown that associative arrays do simplify programs. My own use of associative
- arrays has expanded as I've grown more aware of the range of possibilities.
- I have used associative arrays in applications ranging from development tools
- running in batch mode to production code in real-time systems. Conventional
- arrays access data elements only through integer indexes, but associative
- arrays allow arbitrary types, such as strings, to index the array. This
- seemingly small but powerful addition in capability has made associative
- arrays a mainstay in many of my solutions to programming problems.
-
-
- Bibliography
-
-
- [1] Knuth, Donald E. The Art of Computer Programming, Volume 3: Sorting and
- Searching. Addison-Wesley, 1973.
- Common Hashing Techniques
- Hashing is an alternative approach to search algorithms that rely on key
- comparison. Hashing bases its search on a mathematical calculation performed
- on the search key. The mathematical calculation, known as the hash function,
- calculates the data location using the key as the function input.
- As an example, assume a book is stored by ISBN number, such as ISBN
- 0-19-502402-8.
- If the ISBN number is treated as an integer and a simple hash function is
- defined as f(ISBN)=ISBN modulo 500, the location of each book in a data table
- is determined by inputting its ISBN number into the function. Since the hash
- function indexes the data table, the function's range must correspond to the
- size of the table.
- The above hashing function by itself has a flaw since nothing prevents the
- hash function from selecting the same location for two different books;
- duplicate indexes occur if two ISBN numbers differ by a multiple of 500.
- Duplicate hash values for different keys are known as collisions in hashing
- terminology. You can handle hash collisions one of two ways: 1) prevent the
- occurrence of collisions by using a perfect hash function, or 2) use
- additional processing to handle collisions when they occur.
- Perfect hash functions, which produce no collisions, can only be derived when
- the number of keys is very small and the keys themselves are fixed, as in a
- compiler's list of key words. Since collisions cannot be avoided for most
- applications, writers of hash functions concentrate on reducing the frequency
- of collisions, by producing an even distribution of hash values.
- Most hash function calculations are based either on division, as in the modulo
- operation above, or multiplication, in which selected digits, from the product
- of the index and a constant, are combined into a hash value.
- When the key is not an integer, a hash function usually first reduces it to an
- integer form and then inputs it into a hash calculation. For example, a hash
- function for character strings might first exclusive-OR individual characters
- into a cumulative value, then input that value into a modulo operation.
-
-
- Handling Collisions
-
-
- Hash algorithms can be categorized by the method they use to handle
- collisions. Two common methods are described below.
- Linear probe is the simplest method of handling collisions. If a key hashes to
- a location already containing another value, the linear probe method selects
- the next available empty location. In Figure 1, if 5960010 hashes to location
- 3, the linear probe performs a sequential search that scans the table until it
- finds an empty slot.
- Another common hash algorithm handles collisions by associating a "bucket"
- with each hash value. Two keys colliding at the same location are stored in
- the same bucket (Figure 2). To find a key, the hash function first identifies
- the correct bucket, then compares the residents of the bucket individually
- with the search key. The number of keys stored in any bucket is usually small
- enough such that a linked-list can adequately represent a bucket. A sequential
- search through the linked-list identifies the correct key.
- Figure 1 Linear probe collision handling
- Figure 2 Using hash "buckets" to handle collisions
-
- Listing 1 Associative Array Header File
- #ifndef _AA_INCLUDED
- #define _AA_INCLUDED
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
-
-
- enum AA_KEYTYPE {STRING_KEY, BINARY_KEY};
-
- typedef struct {
- enum AA_KEYTYPE type;
- size_t key_size;
- size_t data_size;
- void **keys;
- void **data;
- size_t current_elements;
- size_t max_elements;
- int (*hash_function)(void *,int,int,enum AA_KEYTYPE);
- } AA;
-
- #define AA_MAX_KEY_SIZE 1024
- #define AA_CURRENT_SIZE(aa) (aa->max_elements)
- #define AA_CURRENT_COUNT(aa) (aa->current_elements)
- #define AA_ACCESS(aa_id,type,key) \
- (*((type *)aa_addr(aa_id,key)))
-
- AA *aa_create (enum AA_KEYTYPE, size_t,
- size_t,size_t,int (*)());
- void *aa_addr(AA *,void *) ;
- void aa_keys(AA *,void ***,int *);
- void aa_delete(AA *,void *);
- unsigned char /* BOOLEAN */ aa_defined(AA *,void *);
- #endif
- /* End of File */
-
-
- Listing 2 The Module Header
- /************** Associative Array Module *************/
- /* Entry Points: aa_create, aa_resize, aa_addr, */
- /* aa_keys, aa_delete, aa_defined */
- /* Test Platforms: HP-UX Version 9.01 */
- /* PC-DOS 3.1 with Turbo C++ */
- /*****************************************************/
- #include "aa.h"
-
- #define DELETED_KEY 1
- #define MAX_KEY_SIZE 1024
- #define NULLKEY(addr) (addr == NULL)
- #define MAXFULL(size) (size * 0.75)
- #define TOO_FULL(count,max) \
- ((count > MAXFULL(max)) ? TRUE : FALSE)
- #define KEY_MATCH(type,key,ptr,size) \
- (type!=STRING_KEY && memcmp(key,*ptr,size)==0)
- #define STRING_KEY_MATCH(t,k,p) \
- (t==STRING_KEY && strcmp((char *)k,*(char **)p)==0)
- #define VALID_KEY(addr) \
- (addr != (void *)NULL && addr != (void *)DELETED_KEY)
- #define INC_SIZE(size) \
- ((size_t)(size * 1.5))
- #define HASH_FUNCTION(a,key,size) \
- (*(a->hash_function))(key,a->key_size,size,a->type)
- #define KEYSIZE(aa,key) \
- (aa->type==STRING_KEY ? strlen(key)+1 : aa->key_size)
- #define KEYCOPY(aa,dest,key) (aa->type==STRING_KEY ? \
- strcpy(*dest,key) : memcpy(*dest,key,aa->key_size))
-
- #define TRUE 1
- #define FALSE 0
-
- typedef unsigned char BYTE;
- typedef unsigned char BOOLEAN;
-
- static int hash_function();
- static size_t prime_size();
-
- /* End of File */
-
-
- Listing 3 Creating the Array
- /*>>>> Create associative array (AA) definition <<<<*/
- AA *aa_create (type,key_size,data_size,size,user_hashf)
- enum AA_KEYTYPE type; /* input -- key type */
- size_t key_size; /* input -- max binary key size */
- size_t data_size; /* input -- max data size */
- size_t size; /* input -- initial size */
- int (*user_hashf)(); /* input -- optional hash func */
- {
- AA * aa;
-
- /* all AA information stored in structure */
- if (aa=(AA *)malloc(sizeof(AA))) {
- size=prime_size(size);
- aa->keys=(void **)calloc(size, sizeof(void *));
- aa->data=(void **)calloc(size, sizeof(void *));
- if (aa->keys && aa->data) { /*if allocation ok*/
- aa->type=type;
- aa->key_size=key_size;
- aa->max_elements=size;
- aa->current_elements=0;
- aa->data_size=data_size;
- if ((aa->hash_function=user_hashf) == NULL)
- aa->hash_function=&hash_function;
- } else { /* failure, release memory */
- if (aa->keys) free((void *)aa->keys);
- if (aa->data) free((void *)aa->data);
- free((void *)aa);
- aa=NULL; /* return NULL on failure */
- }
- }
- return(aa); /* return AA definition */
- }
-
- /*>>>> Get 1st prime larger than request size <<<<*/
- static size_t prime_size(size)
- size_t size; /* start search for prime at size*/
- {
- int divisor,i=(size<5 ? 5 : size);
- i=(i % 2) ? i : i + 1; /* start on odd number */
- do {
- /* try each number which could be exact divisor */
- for (divisor=3; divisor <= i/divisor; divisor+=2)
- if ((i % divisor) == 0) break; /* if !prime */
- if (divisor > (i/divisor)) break; /* if prime */
- } while (i+=2); /* try next odd number */
- return(i); /* return prime number */
-
- }
-
- /* End of File */
-
-
- Listing 4 Accessing the Array
- /*>>>> Return data pointer for specified key <<<<*/
- /* Main interface to store and retrieve AA data */
- void *aa_addr(aa,key)
- AA * aa; /* input -- AA definition */
- void *key; /* input -- lookup key */
- {
- int index;
- void **keyptr, **dataptr;
-
- if (TOO_FULL(aa->current_elements,aa->max_elements))
- if (!aa_resize(aa,INC_SIZE(aa->max_elements)))
- fprintf(stderr,"AA: resize failed\n");
-
- index=hashindex(aa,key); /* index for key & data */
- keyptr=&aa->keys[index];
- dataptr=&aa->data[index];
- if (!VALID_KEY(*keyptr)) { /* if key not in table */
- /* allocate key & data memory; copy/define key */
- *keyptr=(void *)malloc(KEYSIZE(aa,key));
- *dataptr=(void *)calloc(aa->data_size,1);
- if (keyptr && dataptr) { /* if allocation ok */
- KEYCOPY (aa, keyptr, key);
- aa->current_elements++;
- } else { /* allocation failed */
- fprintf(stderr,"AA: key allocation failed\n");
- fflush(stderr); /* probably will crash soon */
- }
- }
- return(*dataptr); /* return pointer to data area */
- }
-
- /* End of File */
-
-
- Listing 5 Hashing the Keys
- /*>>>>>>>>>>>> Default hash function <<<<<<<<<<<<<<<*/
- static int hash_function(key,keysize,maxhash,type)
- void *key; /* input -- key to be hashed */
- int keysize; /* input -- size of key */
- int maxhash; /* input -- max legal hash value */
- enum AA_KEYTYPE type; /* input -- type of input key */
- {
- int i;
- static shifts_initialized=FALSE;
- static BYTE svalues[]={0,24,3,21,5,19,7,17,9,15,11};
- static BYTE sconstants[AA_MAX_KEY_SIZE];
- register unsigned long count=0;
- register BYTE *kp=(BYTE *)key, *end;
- register BYTE *shifts=sconstants;
-
- /* on 1st call init shifts; empirically determined */
- if (!shifts_initialized) {
- for(i=0; i < AA_MAX_KEY_SIZE; i++)
-
- sconstants[i]=svalues[i % sizeof(svalues)];
- shifts_initialized=TRUE;
- }
-
- /* the loops are high runners; need to be fast */
- if (type == STRING_KEY)
- for ( ; *kp != (char) 0; kp++,shifts++)
- count ^= *kp << *shifts;
- else
- for (end=kp+keysize; kp < end; kp++,shifts++)
- count ^= *kp << *shifts;
- return (count % maxhash); /* return hash value */
- }
-
- /*>>> Find index for key; Use hash algorithm. <<<*/
- /*>>> Method: Linear Probe with Double Hashing <<<*/
- static int hashindex (aa,key)
- AA * aa; /* input -- AA definition */
- void *key; /* input -- lookup key */
- {
- int found=FALSE, index;
- void **ptr;
- BOOLEAN first_collision=TRUE;
-
- /* hash function determines where to start search */
- index=HASH_FUNCTION(aa, key, aa ->max_elements);
-
- do { /* do until matching key or empty slot found */
- ptr=&aa->keys[index]; /* get key pointer */
-
- if (NULLKEY(*ptr))
- found=TRUE; /* empty slot */
- /* keys comparisons must consider the key type */
- else if (STRING_KEY_MATCH(aa->type,key,ptr))
- found=TRUE; /* string key found */
- else if (KEY_MATCH(aa->type,key,ptr,aa->key_size))
- found=TRUE; /* non-string key found */
- /* wrong key was found so collision occurred */
- else if (first_collision) {
- /* try double hash on first collision only */
- index=HASH_FUNCTION(aa,key,aa->max_elements-4);
- first_collision=FALSE;
- } else /* collision -- use linear search/probe */
- if (++index == aa->max_elements) /* if end */
- index=0; /* restart search at beginning */
- } while (!found);
-
- return(index); /* return key and data index */
- }
-
- /* End of File */
-
-
- Listing 6 Retrieving the Keys
- /*>>>>>>>>>>>>> Return all keys in AA <<<<<<<<<<<<<<*/
- void aa_keys(aa,keys,num)
- AA * aa; /* input -- AA definition */
- void ***keys; /* output -- array of key pointers */
- int *num; /* output -- number of keys returned */
-
- {
- register int i;
- register void **ptr;
- size_t buf_size=aa->current_elements*sizeof(void **);
-
- if (*keys=(void **)malloc(buf_size)) {
- *num=0; ptr=aa->keys;
- for (i=0; i<aa->max_elements; i++,ptr++)
- if (VALID_KEY(*ptr))
- (*keys)[(*num)++]=*ptr;
- } else /* allocation failed */
- *num=-1;
- }
- /* End of File */
-
-
- Listing 7 Resizing the Array
- /*>>>>>>>>>>>>>>>>>> Resize the AA <<<<<<<<<<<<<<<<<*/
- BOOLEAN aa_resize (aa,newsize)
- AA * aa; /* input -- AA definition */
- size_t newsize; /* input -- new size for AA */
- {
- BOOLEAN rc=TRUE;
- void **newkeys, **newdata, **oldkeys, **olddata;
- int i, oldsize, index;
-
- newsize=prime_size(newsize); /* insure prime size */
- /* alloc new/enlarged key and data tables */
- newkeys=(void **)calloc(newsize, sizeof(void *));
- newdata=(void **)calloc(newsize, sizeof(void *));
- if ((aa->current_elements < MAXFULL(newsize)) &&
- (newkeys && newdata)) {
- oldkeys=aa->keys; /* save old table ptrs */
- olddata=aa->data;
- oldsize=aa->max_elements;
- aa->keys=newkeys; /* put new tables in AA def */
- aa->data=newdata;
- aa->max_elements=newsize;
-
- /* now add/rehash all keys/data into new table */
- for (i=0; i<oldsize; i++) /*scan for valid keys*/
- if (VALID_KEY(oldkeys[i])) {
- index=hashindex(aa,oldkeys[i]);
- aa->keys[index]=oldkeys[i];
- aa->data[index]=olddata[i];
- }
- free((void *)oldkeys); free((void *)olddata);
- } else { /* cleanup on error */
- if (newkeys) free((void *)newkeys);
- if (newdata) free((void *)newdata);
- rc=FALSE;
- }
- return(rc); /* return status, TRUE if successful */
- }
- /* End of File */
-
-
- Listing 8 Deleting and Testing Elements
- /*<<<<<<<<<< Delete an entry from the AA <<<<<<<<<<*/
-
- void aa_delete(aa,key)
- AA * aa; /* input -- AA definition */
- void * key; /* input -- key to delete, with data */
- {
- int index=hashindex(aa,key);
- if (VALID_KEY(aa->keys[index])) {
- free ((void *)aa->keys[index]);
- aa->keys[index]=(void *)DELETED_KEY;
- free ((void *)aa->data[index]);
- aa->data[index]=NULL;
- aa->current_elements--;
- }
- }
-
- /*>>>>>> Return TRUE if key defined in the AA <<<<<<<*/
- BOOLEAN aa_defined(aa,key)
- AA * aa; /* input -- pointer to AA definition */
- void * key; /* input -- key to check if defined */
- {
- return(VALID_KEY(aa->keys[hashindex(aa,key)]));
- }
- /* End of File */
-
-
- Listing 9 Program to Count Word Frequency
- /* Counts words then displays word frequency. */
- /* Input: one word per line read from stdin */
-
- #include "aa.h"
-
- #define WORD_COUNT(word) (*(int *)aa_addr(aa_id,word))
- #define UNUSED 0
-
- main()
- {
- int i, count;
- char **words, word_buffer[100];
- AA *aa_id=aa_create(STRING_KEY,UNUSED,sizeof(int),500,0);
-
- while(gets(word_buffer) != NULL)
- WORD_COUNT(word_buffer)++;
-
- aa_keys(aa_id,(void ***)&words,&count);
- for (i=0; i<count; i++)
- printf(" Word: %-30s Frequency: %d\n",
- words[i],WORD_COUNT(words[i]));
- }
- /* End of File */
-
-
- Listing 10 3D Sparse Array Program
- #include "aa.h"
-
- typedef struct { long i; long j; long k; } INDEX;
- static INDEX t; /* temporary used in SPARSE macro */
- #define SPARSE(x,y,z) (*(t.i=x,t.j=y,t.k=z, \
- (double *)aa_addr(sparse_id,&t)))
- main()
- {
-
- unsigned int i,j,k;
- AA *sparse_id=aa_create(BINARY_KEY,sizeof(INDEX),
- sizeof(double),500,NULL);
- for (i=0; i<=10000; i+=2000)
- for (j=0; j<=10000; j+=2000)
- for (k=0; k<=10000; k+=2000) {
- SPARSE(i,j,k)=(i+j+k) * 0.0001;
- printf("SPARSE(%5d,%5d,%5d)\t=\t%f\n",
- i,j,k,SPARSE(i,j,k));
- }
- }
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Simulating C++ Templates in C and C++
-
-
- John W. Small
-
-
- Since receiving his BSCS from George Mason University in 1985, John W. Small
- has run PSW/ Power Software, a software tools and consulting company. He has
- authored the FlexList C/C ++, Container Lite C++, and COP (C Object
- Programming) tools as well as EB, the Electronic Book, a hypertext
- application. He has been programming in C/C ++ for 10/5 years, respectively,
- as well as in Smalltalk. He can be reached via email at john.small@wdn.com or
- (703) 759-3838 evenings.
-
-
- This article outlines a systematic approach to simulating templates in C and
- C++. You may need to do so because not all C++ translators implement
- templates, and templates are certainly not a part of conventional C. I begin
- by demonstrating why you might want to use function and class templates. Then
- I show how to synthesize function and class templates, with examples in both C
- and C++. If you are familiar only with C, you should learn a little C++ in the
- process.
-
-
- Function Templates
-
-
- Macros in C and C++ allow you to define generic functions such as:
- #define max(x, y) ((x > y) ? x : y)
- However, using the #define preprocessor directive circumvents any strong type
- checking that might be provided by the translator. And it can also introduce
- undesired side effects for the unsuspecting programmer who assumes max(x, y)
- is a function call instead of a macro call.
- Consider:
- t = max(++r, s);
- which expands to
- t = ((++r > s) ? ++r: s)
- If r is greater than or equal to s upon calling max(++r, s), r will be
- incremented twice. The arguments ++r and s are treated simply as token
- sequences by the max macro. The preprocessor substitutes them for the formal
- parameters x and y wherever they appear in the definition of max. Macros are
- thus said to be called by name. The names of the actual arguments passed to
- the macro replace the formal parameters in the expansion process.
- Such problems can be avoided by coding max as a function:
- int max(int x, int y)
- { return (x > y) ? x : y; }
- With this definition, the expression
- t = max(++r, s);
- always leaves r incremented by one instead of sometimes being incremented by
- two. The max function is said to be called by value. The values of ++r and s
- are stored in the auto (local) variables x and y respectively. The value of r
- is incremented before it is stored in x and it can never be erroneously
- incremented again inside max.
- With this solution however, we have lost the ability to define max
- generically. If we need to compare two float values, we would have to code:
- float max(float x, float y)
- { return (x > y) ? x : y; }
- Both the int and float versions of max can be simultaneously defined in C++.
- Unlike C, the newer language allows function names to be overloaded. The types
- of the arguments in the function call dictate which version the C++ translator
- calls.
- C++ also provides a construct known as a function template that allows us to
- define a generic family of functions without sacrificing strong type checking.
- The C++ template approach to defining max would be;
- template <class TYPE>
- inline TYPE max(TYPE x, TYPE y)
- { return (x > y) ? x : y; }
- A function template is introduced by the keyword template followed immediately
- by one or more formal parameters, enclosed in angle brackets, that specify
- types. The function body is the same as a regular function definition except
- that the template's type parameters can appear anywhere in the definition that
- a data type name might normally appear. The following code causes the
- translator to automatically generate three different versions of the max
- function from its template definition. One definition is for int arguments,
- another is for long arguments, and yet another is for float arguments.
- int r, s, t;
- long u, v, w;
- float x, y, z;
- ...
- t = max(++r, s);
- u = max(++v, w);
- z = max(x, y);
- The keyword inline signals to the translator to generate inline code instead
- of invoking a function call each time. Thus you can see that function
- templates require no more overhead than macros but provide strong type
- checking and preclude erroneous side effects. Furthermore you can manually
- override template functions by supplying your own specialization for some
- combination of arguments:
- inline char *max(char* x, char *y)
- { return (strcmp(x,y) >= 0) ? x : y; }
- With this definition, the code sequence:
- char a[] = "one", b[] = "two", *c;
-
- c = max(a, b);
- calls your version of max for strings, preventing the translator from
- generating a version from the max template.
-
-
- Class Templates
-
-
-
- C++ also provides a construct known as a class template, for which there is no
- direct counterpart in C. Suppose you need to implement a stack of strings in C
- (see Listing 1). Our stack of strings includes functions for all the stack
- primitive operations, e.g. full, push, top, and pop. The init function is used
- to initialize a string stack. The main function demonstrates stack usage by
- building a "To Do" list, which is then streamed to the standard output. To
- implement a stack for some other data type you would have to clone this code,
- replacing char * throughout with the desired data pointer type. Of course all
- occurrences of the name StrStack would also have to be replaced with the
- appropriate new stack name.
- The C++ version of Listing 1 is shown in Listing 2. The first thing you should
- notice is that C++ automatically makes a structure tag a type definition as
- well, so that StrStack is now a type. Thus:
- class StrStack { ... };
- is equivalent to:
- typedef class StrStack { ... } StrStack;
- Also, in C++ the keyword class is equivalent to the keyword struct except the
- members of the structure are private in scope by default and thus cannot be
- accessed from outside the class.
- The second thing you should notice is that the functions full, push, top, and
- pop are now public methods of the StrStack class. They don't make the stack
- structure any larger, but the C++ translator can now keep track of which
- functions go with which data structures. The public functions provide a secure
- interface to the hidden object members, providing an important form of
- encapsulation. Users of the StrStack class cannot accidently corrupt the
- member objects in a StrStack class instance (object), for example.
- Furthermore, all member functions have an implicit this pointer to a class
- instance as a hidden formal parameter. To the translator, the definition
- char *pop()
- {
- return (items ? itemPtrs[--items]
- : 0);
- }
- really behaves like:
- char *pop(StrStack *this)
- {
- return (this->items ?
- this->itemPtrs [--this->items]
- : 0);
- }
- Thus, pop implicitly knows which StrStack instance it is going to pop.
- Sometimes the implicit this pointer may need to be explicitly used in an
- expression to distinguish between a data member and a formal parameter or some
- other object, when their names conflict.
- Member functions defined within the class declaration are considered inline
- functions. The function StrStack::push(char *) is not inlined since its
- definition appears outside its class declaration.
- The function StrStack() is a constructor for the class, equivalent to the C
- version's init function. Constructors are used to initialize class instances
- at their point of definition. Thus
- StrStack ToDo;
- appearing in the function main not only allocates memory for the ToDo stack
- but causes the translator to automatically generate a call to StrStack() for
- you.
- Lastly, notice how methods are invoked:
- while (ToDo.top())
- The implicit this pointer of top is automatically set to point to ToDo. The
- C++ version of top() doesn't have to check to see if this is a null pointer.
- By contrast, the C version primitives must always validate stackPtr.
- Listing 3 shows how a class template can be used to parameterize the stack
- class so that it can be reused for any data type without requiring the
- programmer to clone code. The class template is also introduced by the keyword
- template followed immediately by one or more formal parameters, enclosed in
- angle brackets, specifying types or constants. We now use the expression:
- stack<char> ToDo;
- to define our ToDo stack of strings (or character pointers). Since the
- template has no second actual parameter, a default value of 5 is used to limit
- the stack to 5 strings. Defaults can currently be specified only for
- constants, never types. Thus constant parameters with default arguments must
- always appear last in a template's formal parameter list. When generating a
- template class, the translator substitutes the actual parameters supplied or
- defaulted for the template's formal parameters throughout the template model.
- A second stack:
- stack<int, 10> CountDown;
- defines a stack of integer pointers limited to a maximum of ten items. Upon
- encountering a new class specification, the translator will only generate the
- parameterized class if it hasn't already been generated. Please note that a
- class template's out-of-line member function definition can appear in a header
- file, since it is only a model with which the translator can generate the
- actual function definition. Hence a template function definition, in itself,
- generates no code. Template generation is actually a two-part process,
- consisting of generating declarations and definitions.
-
-
- Synthesizing Templates in C++
-
-
- When you invoke a template, it automatically generates both declarations and
- definitions. For example, a function declaration names the function and
- describes its required parameters, while its definition is an algorithmic
- description for which the translator must generate code. Likewise, a data
- structure declaration names its type and specifies its layout but doesn't yet
- reserve any storage. Only when an object is associated for that data type
- might memory be allocated for it.
- The storage class extern turns what would otherwise be an object definition
- into an object declaration -- the object has external linkage with memory
- allocated for it elsewhere. A function declaration without a defining body
- generates no code regardless of the linkage you specify. An inline function
- has a defining body, but generates no code until it is called in a context
- that is not itself an inline function. For our purpose here, I will call all
- these declarations, because they only describe. By contrast, definitions
- generate code or reserve storage for objects.
- In our synthesis of templates, we need to emulate separately the process of
- generating declarations and definitions. Since declarations typically appear
- in header files while definitions appear in source files, our approach will
- place the declarations in a header file and the definitions in a source file.
- The logic for this will be apparent shortly. Listing 4, Listing 5, and Listing
- 6 show how both function and class templates are synthesized for the function
- max and the class stack. Listing 4 is the file that generates declarations,
- Listing 5 the file that generates definitions, and Listing 6 is the source
- file where these emulated templates are used. We'll call our approach to
- emulating templates "form templates."
-
-
- Layout Rules
-
-
- 1) Since more than one form template may appear in a generating file, each
- form template must be encased within its own conditional preprocessor
- directive, as in:
- #ifdef NAME /* open NAME envelope */
- ...
- #endif /* close NAME envelope */
- which I call a form-template envelope. Here, NAME is the name of the function
- or class form template. For C++, function form template envelopes are named
- the same as their first template parameter. See if you can pick out the form
- template envelopes in Listing 4 and Listing 5.
- 2) Form template parameter names must be unique within a generating file. For
- example, the following C++ templates:
- template <class max_TYPE>
- inline max_TYPE max(max_TYPE x, max_TYPE y)
- { return (x > y) ? x : y; }
-
- template <class stack_ITEM,
-
- unsigned stack_MAX_ITEMS = 5>
- class stack { ... }
- have no naming conflicts among their various parameter names -- max_TYPE,
- stack_lTEM, and stack_MAX_ITEMS. Though not a requirement for C++ templates,
- it is for form templates.
- 3) A default value for a constant parameter must be conditionally defined as
- its default value before its class declaration. Both the default value and
- class declaration must be contained within the form template envelope:
- /* open form template envelope */
- #ifdef stack
-
- /* define default value */
- #ifndef stack_MAX_ITEMS
- #define stack_MAX_ITEMS 5
- #endif
-
- class stack {
- stack_ITEM *itemPtrs[stack_MAX_ITEMS];
- ...
- #endif /* close form template envelope */
- 4) A form template doesn't utilize the keyword template, nor is there a formal
- parameter list. Otherwise the form template model is exactly the same as the
- standard C++ template model. There is one additional exception: scope
- resolution names must drop their parameterizing arguments. For example:
- int stack<stack_ITEM, stack_MAX_ITEMS>::
- push(stack_ITEM *itemPtr)
- is changed to read
- int stack::push(stack_ITEM *itemPtr)
- 5) A form template header file, such as Listing 4, always concludes with the
- conditional parameter wrapup section. This section undefines all form template
- parameters and names unless the header is being included by its corresponding
- form template source file, the file that generates definitions. For example:
- /* decl_gen.hpp */
- ....
- /* parameter wrapup section */
- #ifndef def_gen_cpp /* defined in def_gen.cpp */
- #undef max_TYPE
- #undef stack_ITEM
- #undef stack_MAX_ITEMS
- #undef stack
- #endif
- 6) A form template source file, such as listing 5, must first include its
- corresponding header file. For example:
- /* def_gen.cpp */
- #define def_gen_cpp
- #include "decl_gen.hpp"
- #undef def_gen_cpp
- Notice how the include directive is sandwiched between the definition and
- undefinition of the source-file macro id. This is the macro id used to exclude
- the parameter wrapup section described previously in rule 5.
- 7) Much like the form template header file, the source file terminates with a
- parameter wrapup section. In this case, however, the section is unconditional.
- ...
- /* parameter wrapup section */
- #undef max_TYPE
- #undef stack_ITEM
- #under stack_MAX_ITEMS
- #under stack
-
-
- Rules for Using Templates
-
-
- 1) In order to use a form template to generate either declarations or
- definitions, you must define its name. For a C++ function form template you
- define its first parameter instead. For example:
- #define max_ITEM int
- ...
- #define stack stack_strings
- The above code snippet indicates we are about to generate the function max for
- integers and a parameterized class stack named stack_strings.
- 2) All of the various form template parameters must also be defined as
- required. For example:
- #define max_TYPE int
- #define stack_ITEM char
- 3) Immediately following the form template envelope names and parameters,
- include the declarations generating file. To generate both declarations and
- definitions, include the definitions generating file instead, as in:
- #define max_TYPE int
-
- #define stack_ITEM char
- #define stack stack_strings
- #include "def_gen.cpp"
- At this point we have generated declarations and definitions for:
- int max(int x, int y);
- and
- stack_strings; // i.e. stack<char, 5U>
- with all form template envelope names and parameters left undefined!
- 4) To generate an additional type from the same form template, simply repeat
- the process.
- define stack_ITEM int
- #define stack_MAX_ITEMS 10U
- #define stack stack_int_10U
- #include "def_gen.cpp"
- At this point we have also generated a declaration and definition for:
- stack_int_10U; // i.e. stack<int, 1OU>
- The naming conventions used for these stacks has no significance as far as the
- form template generating mechanism is concerned.
-
-
- Synthesizing Templates in C
-
-
- Listing 7, Listing 8, and Listing 9 repeat our example, this time in C. Please
- note the following differences from the C++ approach:
- 1) Function form template envelopes have the same name as the function itself:
- /* max function envelope (see Listing 7) */
- #ifdef max/* C uses function name test */
- extern max_TYPE max(max_TYPE x, max_TYPE y);
- #endif
- Since C doesn't allow for inlined functions, max is declared as external in
- the header file and defined in the source file as:
- /* max function envelope (see Listing 8) */
- #ifdef max /* C uses function name test */
- max_TYPE max(max_TYPE x, max_TYPE y)
- { return (x > y) ? x : y; }
- #endif
- 2) Neither does C allow for function name overloading, so in order to generate
- a max function we must redefine its name as well as its parameter.
- #define max max_iii /* see Listing 9 */
- #define max_TYPE int
- ...
- #include "def_gen.c"
- The convention I use here, max_iii, indicates a function generically named max
- returning an integer and taking two integer parameters. In order for C++ to
- allow for function-name overloading, the C++ translator generates unique
- mangled names based on return and parameter types. You can think of max_iii as
- a hand-mangled name. You might name a function for floats max_fff. However,
- you must call the proper function, max_iii or max_fff, unlike in C++.
- 3) Since C doesn't support member functions, you must define a scoping macro
- to support pseudo membership, as in:
- #define stack_ITEM char /* see
- Listing 9 */
- #define stack stack_char
- #define stack_scope(memFnc)
- stack_char_ ## memFnc
- #include "def_gen.c"
- The macro stack_scope is used in both the form template header and source
- files, as in:
- extern int stack_scope(push) /* see
- Listing 7 */
- (struct stack * stackPtr,
- stack_ITEM * itemPtr);
-
- int stack_scope(push) /* see Listing
- 8 */
- (struct stack * stackPtr, stack_ITEM * itemPtr)
- {...}
- to generate stack-scoped function names. You can think of this as a different
- sort of name mangling. The function name is mangled to reflect its pseudo
- structure membership instead of its variant parameter types. In order to call
- a pseudo member function you must specify its scope mangled name, as in:
- /* see Listing 9 */
- stack_char_push(&ToDo,"wash car");
-
- The struct scoping macro must be undefined in the parameter wrapup sections.
- See the tail of Listing 7 and Listing 8.
-
-
- Conclusion
-
-
- The "form template" approach shown here can be used in a nested fashion to
- implement any structure that can be described with conventional C++ templates.
- No longer must you limit your designs to non-parameterized types simply
- because your C++ translator doesn't yet support templates. Likewise C
- application design can now also benefit from the C++ template concept.
-
- Listing 1 Implementing a stack of strings in C
- struct StrStack {
- char * itemPtrs[5U];
- unsigned items;
- };
-
- void init(struct StrStack * stackPtr)
- {
- if (stackPtr)
- stackPtr->items = 0U;
- }
-
- int full(struct StrStack * stackPtr)
- {
- if (stackPtr && stackPtr->items < 5U)
- return 0;
- return 1;
- }
-
- int push(struct StrStack * stackPtr, char * itemPtr)
- {
- if (!full(stackPtr) && itemPtr) {
- stackPtr->itemPtrs[stackPtr->items++]
- = itemPtr;
- return 1;
- }
- return 0;
- }
-
- char * top(struct StrStack * stackPtr)
- {
- if (stackPtr && stackPtr->items)
- return stackPtr->itemPtrs[stackPtr->items-1];
- return (char *) 0;
- }
-
- char * pop(struct StrStack * stackPtr)
- {
- if (stackPtr && stackPtr->items)
- return stackPtr->itemPtrs[--stackPtr->items];
- return (char *) 0;
- }
-
- #include <stdio.h>
-
- main()
- {
- struct StrStack ToDo;
-
- init(&ToDo);
- (void) push(&ToDo,"wash car");
-
- (void) push(&ToDo,"cut grass");
- (void) push(&ToDo,"buy groceries");
- (void) push(&ToDo,"cash paycheck");
- while (top(&ToDo))
- (void) printf("%s\n",pop(&ToDo));
- return 0;
- }
- /* End of File */
-
-
- Listing 2 The C++ version of Listing 1
- class StrStack {
- char * itemPtrs[5U];
- unsigned items;
- public:
- StrStack() { items = 0U; }
- int full() { return !(5U - items); }
- unsigned depth() { return items; }
- int push(char * itemPtr);
- char * top()
- { return (items? itemPtrs[items-1] : 0); }
- char * pop()
- { return (items? itemPtrs[--items] : 0); }
- };
-
- int StrStack::push(char * itemPtr)
- {
- if (!full() && itemPtr) {
- itemPtrs[items++] = itemPtr;
- return 1;
- }
- return 0;
- }
-
- #include <iostream.h>
- #include <iomanip.h>
-
- main()
- {
- StrStack ToDo;
-
- ToDo.push("wash car");
- ToDo.push("cut grass");
- ToDo.push("buy groceries");
- ToDo.push("cash paycheck");
- while (ToDo.top())
- cout << ToDo.pop() << endl;
- return 0;
- }
- /* End of File */
-
-
- Listing 3 Using a class template to parameterize the stack class
- template <class TYPE>
- inline TYPE max(TYPE x, TYPE y)
- { return (x > y) ? x : y; }
-
- template <class ITEM, unsigned MAX_ITEMS = 5>
- class stack {
-
- ITEM * itemPtrs[MAX_ITEMS];
- unsigned items;
- public:
- stack() ( items = 0U; }
- int full() { return !(MAX_ITEMS - items); }
- unsigned depth() { return items; }
- int push(ITEM * itemPtr);
- ITEM * top()
- { return (items? itemPtrs[items-1] : 0); }
- ITEM * pop()
- { return (items? itemPtrs[--items] : 0); }
- };
-
- template <class ITEM, unsigned MAX_ITEMS>
- int stack<ITEM,MAX_ITEMS>::push(ITEM * itemPtr)
- {
- if (!full() && itemPtr) {
- itemPtrs[items++] = itemPtr;
- return 1;
- }
- return 0;
- }
-
- #include <iostream.h>
- #include <iomanip.h>
-
- main()
- {
- stack<char> ToDo;
-
- ToDo.push("wash car");
- ToDo.push("cut grass");
- ToDo.push("buy groceries");
- ToDo.push("cash paycheck");
- while (ToDo.top())
- cout << ToDo.pop() << endl;
-
- stack<int,10U> CountDown;
-
- for (int i = 1; CountDown.push(new int(i)); i++);
- while (CountDown.top()) {
- cout << *CountDown.top() << " ";
- delete CountDown.pop();
- }
- cout << "Blast Off!" << endl;
- return 0;
- }
- /* End of File */
-
-
- Listing 4 Declaring synthesized templates in C++
- /* decl_gen.hpp -- declarations generating file */
-
- /* max function envelope */
- #ifdef max_TYPE /* C++ uses first parameter test */
- inline max_TYPE max(max_TYPE x, max_TYPE y)
- { return (x > y) ? x : y; }
- #endif
-
-
- #ifdef stack /* open stack class envelope */
-
- #ifndef stack_MAX_ITEMS /* default parameters */
- #define stack_MAX_ITEMS 5U
- #endif
-
- class stack {
- stack_ITEM * itemPtrs[stack_MAX_ITEMS];
- unsigned items;
- public:
- stack() { items = 0U; }
- int full() { return !(stack_MAX_ITEMS - items); }
- unsigned depth() { return items; }
- int push(stack_ITEM * itemPtr);
- stack_ITEM * top()
- { return (items? itemPtrs[items-1] : 0); }
- stack_ITEM * pop()
- { return (items? itemPtrs[--items] : 0); }
- };
-
- #endif /* close stack class envelope */
-
- #ifndef def_gen_cpp /* parameter wrapup section */
- #undef max_TYPE
- #undef stack
- #undef stack_ITEM
- #undef stack_MAX_ITEMS
- #endif
- // End of File
-
-
- Listing 5 Defining synthesized templates in C++
- /* def_gen.cpp -- definitions generating file */
-
- #define def_gen_cpp
- #include "decl_gen.hpp"
- #undef def_gen_cpp
-
- #ifdef stack /* open stack class envelope */
-
- int stack::push(stack_ITEM * itemPtr)
- {
- if (!full() && itemPtr) {
- itemPtrs[items++] = itemPtr:
- return 1;
- }
- return 0;
- }
-
- #endif /* close stack class envelope */
-
- /* parameter wrapup section */
- #undef max_TYPE
- #undef stack
- #undef stack_ITEM
- #undef stack_MAX_ITEMS
-
- // End of File
-
-
-
- Listing 6 Using synthesized templates in C++
- /* formtemp.cpp -- use form templates */
-
- /*
- Generate declarations and definitions for
-
- int max(int,int);
- stack<char>
- /*
- #define max_TYPE int
- #define stack_ITEM char
- #define stack stack_strings
- #include "def_gen.cpp"
-
- /*
- Generate declarations and definitions for
-
- stack<int,10U>
- */
- #define stack_ITEM int
- #define stack_MAX_ITEMS 10U
- #define stack stack_int_10U
- #include "def_gen.cpp"
-
- #include <iostream.h>
- #include <iomanip.h>
-
- main()
- {
-
- cout << "Which is greater? "
- << 4 <<"or"<<"5<<"?";
- cout <<" Answer: "
- << max(4,5) << "!" << endl;
-
- stack_strings ToDo;
-
- ToDo.push("wash car");
- ToDo.push("cut grass");
- ToDo.push("buy groceries");
- ToDo.push("cash paycheck");
- while (ToDo.top())
- cout << ToDo.pop() << endl;
-
- stack_int_10U CountDown;
-
- for (int i = 1; CountDown.push(new int(i)); i++);
- while (CountDown.top()) {
- cout << *CountDown.top() << " ";
- delete CountDown.pop();
- }
- cout << "Blast Off!" << endl;
- return 0;
- }
- // End of File
-
-
- Listing 7 Declaring synthesized templates in C
-
- /* decl_gen.h -- declarations generating file */
-
- /* max function envelope */
- #ifdef max /* C uses function name test */
- extern max_TYPE max(max_TYPE x, max_TYPE y);
- #endif
-
- #ifdef stack /* open stack struct envelope */
- #ifdef stack_MAX_ITEMS /* default parameters */
- #define stack_MAX_ITEMS 5U
- #endif
-
- struct stack {
- stack_ITEM * itemPtrs[stack_MAX_ITEMS];
- unsigned items;
- };
-
- extern void stack_scope(init)
- (struct stack * stackPtr);
- extern int stack_scope(full)
- (struct stack * stackPtr);
- extern int stack_scope(push)
- (struct stack * stackPtr, stack_ITEM * itemPtr);
- extern stack_ITEM * stack_scope(top)
- (struct stack * stackPtr);
- extern stack_ITEM * stacks_cope(pop)
- (struct stack * stackPtr);
-
- #endif /* close stack struct envelope */
-
- #ifndef def_gen_c /* parameter wrapup section */
- #undef max
- #undef max_TYPE
- #under stack
- #undef stack_ITEM
- #undef stack_MAX_ITEMS
- #undef stack_scope
- #endif
- /* End of File */
-
-
- Listing 8 Defining synthesized templates in C
- / * def_gen.c -- definitions generating file * /
-
- #define def_gen_c
- #include "decl_gen.h"
- #undef def_gen_c
-
- /* max function envelope */
- #ifdef max /* C uses function name test */
- max_TYPE max(max_TYPE x, max_TYPE y)
- { return (x > y) ? x: y; }
- #endif
-
- #ifdef stack /* open stack struct envelope */
-
- void stack_scope(init)(struct stack * stackPtr)
- {
- if (stackPtr)
-
- stackPtr->items = 0U;
- }
-
- int stack_scope(full)(struct stack * stackPtr)
- {
- if (stackPtr && stackPtr->items < stack_MAX_ITEMS)
- return 0;
- return 1;
- }
-
- int stack_scope(push)
- (struct stack * stackPtr, stack_ITEM * itemPtr)
- {
- if (!stack_scope(full)(stackPtr) && itemPtr) {
- stackPtr->itemPtrs[stackPtr->items++]
- = itemPtr;
- return 1;
- }
- return 0;
- }
-
- stack_ITEM * stack_scope(top)(struct stack * stackPtr)
- {
- if (stackPtr && stackPtr->items)
- return stackPtr->itemPtrs[stackPtr->items-1];
- return (stack_ITEM *) 0;
- }
-
- stack_ITEM * stack_scope(pop)(struct stack * stackPtr)
- {
- if (stackPtr && stackPtr->items)
- return stackPtr->itemPtrs[--stackPtr->items];
- return (stack_ITEM *) 0;
- }
-
- #endif /* close stack struct envelope */
-
- #undef max
- #undef max_TYPE
- #under stack
- #undef stack_ITEM
- #undef stack_MAX_ITEMS
- #under stack_scope
- /* End of File */
-
-
- Listing 9 Using synthesized templates in C
- /* formtemp.c -- use form templates */
-
- /*
- Generate declarations and definitions for
-
- int max(int,int);
- stack<char>
- */
- #define max max_iii
- #define max_TYPE int
- #define stack_ITEM char
- #define stack stack_char
-
- #define stack_scope(memFnc) stack_char_ ## memFnc
- #include "def_gen.c"
-
- /*
- Generate declarations and definitions for
-
- stack<int,10U>
- */
- #define stack_ITEM int
- #define stack MAX_ITEMS 10U
- #define stack stack_int_10U
- #define stack_scope(memFnc) stack_int_10U_ ## memFnc
- #include "def_gen.c"
-
- #include <stdio.h>
- #include <stdlib.h>
-
- main()
- {
- struct stack_char ToDo;
- struct stack_int_10U CountDown;
- int i, * iptr;
-
- (void) printf("Which is greater? %d or %d?"
- "Answer: %d!\n",4,5,max_iii(4,5));
-
- stack_char_init(&ToDo);
- stack_char_push(&ToDo,"wash car");
- stack_char_push(&ToDo,"cut grass");
- stack_char_push(&ToDo,"buy groceries");
- stack_char_push(&ToDo,"cash paycheck");
- while (stack_char_top(&ToDo))
- (void) printf("%s\n",stack_char_pop(&ToDo));
-
- stack_int_10U_init(&CountDown);
- for (i = 1; !stack_int_10U_full(&CountDown); i++)
- {
- iptr = (int *) malloc(sizeof(int));
- if (!iptr)
- break;
- * iptr = i;
- (void) stack_int_10U_push(&CountDown,iptr);
- }
- while (stack_int_10U_top(&CountDown)) {
- (void) printf("%d ",
- * stack_int_10U_top(&CountDown));
- free(stack_int_10U_pop(&CountDown));
- }
- (void) printf("Blast Off!\n");
- return 0;
- }
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- EMS Professional Shareware Libraries -- Utilities for C/C++
-
-
- Bob Swart
-
-
- Bob Swart is a professional software developer and free-lance technical author
- using Borland Pascal, C++, and Delphi. In his spare time he likes to watch
- video tapes of Star Trek The Next Generation with his 9-month old son Erik
- Mark Pascal.
-
-
- EMS Professional Shareware Libraries are a comprehensive collection of public
- domain, shareware, and free products supporting PC professionals in many
- speciality areas like Pascal, ASM, Windows, (Visual) Basic, Spreadsheets,
- Databases (Access, dBase, Clipper, FoxPro), and of course C/C++.
- Each library consists of a large collection of .ZIP archives (more than 1,600+
- different files in the Utility Library for C/C++, on 99 (!) 1.44 MB diskettes
- or one CD-ROM). The C/C++ Utility Directory is the result of hundreds of hours
- of data collection, checking, and preparation by C experts Mark Harris and
- Bill Gerrard.
- Typical librarians, such as Harris and Gerrard, are specialists with a strong
- interest in collecting and organizing information relating to their field (in
- this case, C and C++). Librarians are typically BBS addicts who like to spend
- lots of hours calling local and long distance BBSs, online services (such as
- CompuServe, AOL, GEnie), and FPT sites on the Internet. They download
- everything relating to their subject area and write descriptions into a
- database EMS provides. The librarians also enter address, phone, and e-mail
- information, which EMS then uses to contact the authors for more recent
- versions, description corrections, etc.
- For most of EMS's collections (including the C/C++ Library) the librarian will
- also enter information on all related commercial products they discover in
- magazine articles and advertisements. EMS does not place these commercial
- products on the CD-ROM, but they do maintain contact information and
- descriptions in the database (included on the CD-ROM, also available
- separately if you buy the floppies) so that customers have "one-stop shopping"
- for virtually every C/C++ product that might solve the problem they face that
- day.
- The EMS Library contains programs and complete source code, under the
- following categories: Arrays, Benchmark, Binary Tree, Bit Manipulation, AI,
- Bugs, Code Analysis, Communication, Compiler, Container & BIDS, Database,
- Date/Time, Debug, DOS, Editor, Graphics, Keyboard, Linked List, Mathematics,
- Memory Mgmt., MFC, Mouse, Multitask, Network, NT, OS/2, OWL, Printer, Screen,
- Sound, Streams, String, Template, Turbo Vision, User Interface, Virtual
- Object, and Windows.
- But believe me, this library's sheer size would make describing what you could
- find almost as difficult as finding something in it -- if it weren't for the
- additional database directory EMS supplies for each library. The current
- database directory consists of a single 1.2 MB dBASE III+ compatible .DBF
- file. You can view this file with dBase, or use the supplied program from EMS.
- CUTIL, for use with the C/C++ library, allows you to search for products by
- vendor, type, product name, or free-text search, and will let you view the
- contents of the actual ZIP file from within the database index program.
- Whenever you have one or more hits, you get the product name, file name,
- release date, size, producer, address (including e-mail), phone/fax numbers,
- and description.
- The Library also includes a README file that contains the names of several
- dedicated C/C++ BBSs, book publishers, and publications (including C/C++ Users
- Journal and Windows/DOS Developer's Journal).
- The Directory and Library are updated six times per year. Previous purchasers
- are eligible for a 25 percent reduction from the current list price, which, as
- of this writing, is $25.00 for the directory and $149.00 for the library on 99
- diskettes, or $59.50 for directory plus library on CD-ROM (with many
- additional products).
- EMS also sells a C++ (only) version of the library, which consists of just 772
- files on 53 diskettes for $99.50. The $59.50 CD-ROM contains the full C/C++
- version, so you'd better buy a CD-ROM player now!
- Product Information
- Title: Utility Library for C/C++
- Librarians: Mark Harris and Bill Gerrard
- Files: more than 1,600
- Price: $59.50 (CD-ROM), $149 (99 diskettes)
- Publisher: EMS Professional Shareware
- 4505 Buckhurst Court
- Olney, MD 20832-1830, USA
- +1 301 924-3594, FAX: +1 301 963-2708
- e-mail: eengelmann@worldbank.org
- Web: http://www.paltech.com/ems/ems.htm
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Standard C/C++
-
-
- Implementing <strstream>
-
-
-
-
- P.J. Plauger
-
-
- P.J. Plauger is senior editor of C/C++ Users Journal. He is convener of the
- ISO C standards committee, WG14, and active, on the C++ Committee, WG21. His
- latest books are The Draft Standard C++ Library, and Programming on Purpose
- (three volumes), all published by Prentice-Hall. You can reach him at
- pjp@plauger.com.
-
-
-
-
- Introduction
-
-
- I introduced the header <strstream> last month and showed a variety of ways to
- use the classes it defines. (See "Standard C/C++: The Header <strstream>,"
- CUJ, January 1995.) Among other things, it defines the class istrstream, which
- is derived from istream to help you extract from a character sequence stored
- in memory. Thus, you can write code like:
- istrstream strin("1 4 7 2 5 8 0
- 3 6 9");
- int i;
- while (strin >> i)
- try_button(i);
- to "read" a constant string just as if it were a file.
- Similarly, class ostrstream is derived from ostream to help you insert into a
- character sequence stored in memory. You can construct a string as if writing
- to a file, for example, then read it later with an istrstream object
- controlling the same stream buffer, as above. For the special magic involved,
- both make use of the stream buffer class strstreambuf. As with all stream
- buffers, it is derived from streambuf, in this case to manage such in-memory
- character sequences. (See "Standard C/C++: The Header <streambuf>," CUJ, June
- 1994.)
- The net effect is that the classes defined in <strstream> let you read and
- write these character sequences with exactly the same machinery you use to
- read and write external files in C++. You can, of course, perform much the
- same operations with the Standard C library functions sprintf and sscanf,
- declared in <stdio.h>. But those older functions require you to use different
- notation for reading and writing in-memory character sequences rather than
- external files. And they don't work with the inserter and extractor machinery
- of C++. The classes defined in <strstream> offer an obvious notational
- advantage, as well as better information hiding.
- My goal this month is to show you one way to implement these classes. It is
- part of the implementation I have been presenting for the past year, which is
- based on the draft C++ Standard going into the March 1994 meeting of
- WG21/X3J16. That in turn was based heavily on the existing header
- <strstream.h> which is still widely used as part of the iostreams package of
- library classes. If you know current C++ practice, much of what you see here
- will be familiar territory.
- In more recent drafts of the C++ Standard, many headers have been
- "templatized." The Standard C++ library now defines general templates that
- describe iostreams operations for an arbitrary "character" type T. To
- reproduce the existing functionality, the library instantiates these general
- templates for T defined as type char. The net result is much the same, but the
- machinery -- and the notation -- is now far more elaborate.
- The header <strstream> has most recently been exempted from this treatment.
- The committee favors its newly minted header <sstream>, which does much the
- same thing as <strstream> but works with templatized strings of arbitrary
- character types. (I discuss the char version of the newer header next month.)
- The older header is retained, in the interest of preserving existing code, but
- as a sort of second-class citizen. For all its idiosyncracies, I personally
- find it a handy way to fiddle with small sequences of type char. So what you
- see here is still quite useful. And it is still destined to be part of the
- final Standard C++ library.
-
-
- The Header File
-
-
- Listing 1 shows the header file that implements strstream. I've discussed some
- of the peculiar notation in past columns, but I've also made a few changes as
- compilers evolve. Briefly:
- The macro _BITMASK defines a "bitmask" type. (See "Standard C/C++: The Header
- <ios>," CUJ, May 1994.) It expands differently depending on whether the
- translator supports overloading on enumerated types, a fairly recent addition
- to the C++ language. I later discovered a need to defer the definitions of the
- overloaded functions, for bitmask types nested inside classes as is the case
- here. Thus, the macro _BITMASK_OPS supplies these deferred definitions.
- The type bool has even more recently been added to the C++ language. It
- represents the true/false value of a test expression, such as a comparison
- operator. Until recently, I provided the typedef _Bool as a placeholder. But
- since some translators now supply this type, I've brought my code more up to
- date. (For older translators, bool is a defined type, not a keyword.)
- The macro _HAS_SIGNED_CHAR expands to a nonzero value for translators that
- treat char and signed char as distinct types. All translators are supposed to,
- but many still do not.
- I have added to the header two macros to specify in one place two values that
- are judgement calls. Both deal with the number of characters to allocate when
- creating or extending a character sequence:
- _ALSIZE, the initial number of bytes to allocate, absent any hints to the
- contrary (currently 512)
- _MlNSIZE, the minimum number of additional bytes to allocate when extending a
- sequence (currently 32)
- You may well have reasons to alter either or both of these values, based on
- what you know about storage size and granularity on a given implementation.
- The bitmask type _Strstate, defined within the class strstreambuf, describes
- the internal state of such an object. Much of the state information is spelled
- out in detail by the draft C++ Standard. I have added, however, the element
- _Noread, which is not used by the classes defined in <strstream>. Adding it
- here greatly simplifies the implementation of the classes defined in <sstream>
- (next month). The meaning of each of the _Strstate elements is:
- _Allocated, set when the character sequence has been allocated
- _Constant, set when the character sequence is not to permit insertions
- _Dynamic, set when the character sequence can grow on demand
- _Frozen, set when the character sequence has been frozen (should not be
- deleted by the destructor)
- _Noread, set when the character sequence is not to permit extractions (is
- write only)
- I developed the protected secret member function strstreambuf::_Init as a way
- to handle all possible constructors, including those for class stringbuf,
- defined in the header <sstream>. Similarly, the protected secret member
- function strstreambuf::_Tidy does all the work of the destructor. It is also
- used to advantage in class stringbuf. (See the discussion of Listing 2,
- below.)
- Most of the objects stored within a strstreambuf object are what you might
- expect. _Strmode holds the state information. Alsize holds the current
- allocated sequence length. _Palloc and _Pfree point at the functions that
- allocate and free storage, if they are specified when the object is
- constructed.
- But there are also two additional private member objects. Each solves a
- different problem in managing accesses to the controlled character sequence.
- _Penadsave stores the end pointer for the output sequence while the stream
- buffer is frozen. (See the discussion of Listing 4, below.) _Seekhigh stores
- the highest defined offset encountered so far within the character sequence.
- The code updates its stored value in several places when that value must be
- made exact.
-
-
- Workhorse Functions
-
-
-
- Listing 2 shows the file strstrea.c. It defines three of the functions you are
- likely to need any time you declare an object of class strstreambuf -- its
- destructor, _Init, and _Tidy. Two of the three functions are straightforward,
- but _Init warrants a bit of study. It selects among multiple forms of
- initialization by an intricate analysis of its arguments:
- If gp (the "get" pointer) is a null pointer, then n (the size argument) is a
- suggested initial allocation size.
- Otherwise, if mode has the bit _Dynamic set, then the initial character
- sequence is copied from one controlled by a string object. The function copies
- n characters beginning at gp. The calling string constructor can independently
- inhibit insertions (_Constant) and/or extractions (_Norend).
- Otherwise, the character sequence resides in an existing character array
- beginning at gp. If n is less than zero, the sequence is assumed to be
- arbitrarily large (INT_MAX characters). If n is zero, the array is assumed to
- contain a null-terminated string, which defines the character sequence. If n
- is greater than zero, it is taken as the length of the character sequence. The
- function defines an output stream only if pp (the "put" pointer) is not a null
- pointer and lies within the character sequence.
- Be warned that this code is extremely fragile. Partly, it reflects the
- complexities of the numerous strstreambuf constructors (which I described last
- month). Partly, it is made larger by the inclusion of support for stringbuf
- constructors. But the code also enforces delicate streambuf semantics that are
- hard to spell out in detail. Tinker cautiously.
- Listing 3 shows the file strstpro.c. It defines three functions that override
- streambuf virtual member functions to insert and extract characters --
- overflow, pbackfail, and underflow. The inherited definition of uflow is
- adequate, so no override occurs here. (See "Standard C/C++: The Header
- <streambuf>," CUJ, June 1994.) Once again, two of the three functions are
- straightforward. Only overflow demands closer study.
- It is the business of overflow to "make a write position available," then
- insert the argument character into it. If the write position is already
- available, or if none can be made available, the function has an easy job of
- it. The hard part comes when the function must extend, or initially create,
- storage for the character sequence. It must then determine the size of any
- existing sequence (osize) and the desired new size (nsize). Then it can try to
- allocate the new storage, copy over any existing sequence, and free an
- existing sequence that was also allocated. Finally, it must determine new
- settings for the streambuf pointers, using some very finicky arithmetic.
-
-
- Other Functions
-
-
- Listing 4 shows the file strstfre.c, which defines the member function
- strstreambuf::freeze. Here is where the addition of the member object
- strstreambuf::_Pendsave saves the day. A frozen buffer must not permit
- insertions, but that is not an easy thing to prevent. The streambuf public
- member functions won't look past the pointers themselves if they indicate that
- a write position is available. So the trick is to make the output stream
- appear empty for a frozen stream buffer by jiggering the end pointer.
- _Pendsave stores the proper value for later restoration, should the stream
- buffer be unfrozen.
- Listing 5 shows the file strstpos.c. It defines the two functions that
- override streambuf virtual member functions to alter the stream position --
- seekoff and seekpos. The often critical value in both functions is the member
- object strstrambuf::_Seekhigh. It is updated as needed to reflect the current
- "end," or high-water mark, of the character sequence. That value determines
- offsets relative to the end (way equals ios::end), as well as an upper bound
- for valid stream offsets. The logic of both functions is otherwise simple but
- tedious.
- And that concludes the source code for class strstreambuf. The two remaining
- classes defined in <strstream> are derived from the classes istream and
- ostream to assist in controlling inmemory character streams. I described how
- to use both istrstream and ostrstream last month. As you can see from Listing
- 1, most of the member functions are small and hence defined as inline.
- Listing 6 shows the file istrstre.c. It defines the destructor for class
- istrstream, which is the only member function not defined inline within the
- class. And Listing 7 shows the file ostrstre.c. It defines the destructor, and
- a moderately messy constructor, for class ostrstream. I put the constructor
- here mostly to hide the call to the function strlen, declared in <string.h>.
- It is not permissible to include the C header that declares it in <strstream>
- and I didn't want to make up a version of the function with a secret name.
- Again this is the only source code for member functions of class ostrstream
- not defined inline within the class.
- While there are a few tricky spots in the implementation of the stream buffer,
- most of the code that implements the header <strstream> is small and
- straightforward. You will find much the same story when we visit other
- specialized stream buffers derived from class streambuf and its brethren. It
- is a tribute to the basic design of iostreams that this is so.
- This article is excerpted in part from P.J. Plauger, The Draft Standard C++
- Library, (Englewood Cliffs, N.J.: Prentice-Hall, 1995).
-
- Listing 1 The header <strstream>
- // strstream standard header
- #ifndef _STRSTREAM_____LINEEND____
- #define _STRSTREAM_____LINEEND____
- #include <istream>
- #include <ostream>
- // constants
- const int _ALSIZE = 512; // default allocation size
- const int _MINSIZE = 32; // minimum allocation size
- // class strstreambuf
- class strstreambuf = public streambuf {
- public:
- enum __Strstate {_Allocated = 1, _Constant = 2,
- _Dynamic = 4, _Frozen = 8, _Noread = 16,
- _Strzero = 0};
- _BITMASK(_Strstate, _Strstate);
- strstreambuf(streamsize _N = 0)
- {_Init(_N); }
- strstreambuf(void *(*_A)(size_t), void (*_F)(void *))
- {_Init(), _Palloc = _A,_Pfree =_F; }
- strstreambuf(char *_G, streamsize _N, char *_P = 0,
- _Strstate _S = _Strzero)
- {_Init(_N, _G, _P, _S); }
- strstreambuf(unsigned char *_G, streamsize _N,
- unsigned char *_P = 0)
- {_Init(_N, (char *)_G, (char *)_P); }
- strstreambuf(const char *_G, streamsize _N)
- {_Init(_N, (char*)_G, 0, _Constant); }
- strstreambuf(const unsigned char *_G, streamsize _N)
- {_Init(_N, (char *)_G, 0, _Constant); }
- virtual ~strstreambuf();
- void freeze(bool = 1);
- char *str()
- {freeze(); return (gptr()); }
- streamsize pcount() const
- {return (pptr() == 0 ? 0 : pptr() - pbase()); }
- #if _HAS_SIGNED_CHAR
- strstreambuf(signed char *_G, streamsize _N,
- signed char *_P = 0)
-
- {_Init(_N, (char *)_G, (char *)_P); }
- strstreambuf(const signed char *_G, streamsize _N)
- {_Init(_N, (char *) _G. 0, _Constant); }
- #endif /* _HAS_SIGNED_CHAR */
- protected:
- virtual int overflow(int = EOF);
- virtual int pbackfail(int = EOF);
- virtual int underflow();
- virtual streampos seekoff(streamoff, ios::seekdir,
- ios::openmode = ios::in ios::out);
- virtual streampos seekpos(streampos,
- ios::openmode = ios::in ios::out);
- void_Init(int = 0, char * = 0, char * = 0,
- _Strstate = _Strzero);
- void_Tidy();
- _Strstate _Strmode;
- private:
- char *_Pendsave, *_Seekhigh;
- int _Alsize;
- void *(*_Palloc)(size_t);
- void (*_Pfree)(void *);
- };
- _BITMASK_OPS(strstreambuf::_Strstate)
- // class istrstream
- class istrstream : public istream {
- public:
- istrstream(const char *_S)
- : istream(&_Sb), _Sb(S, 0) {}
- istrstream(const char *_S, streamsize _N)
- : istream(&_Sb), Sb(_S, _N) {}
- istrstream{char * _S)
- : istream(&_Sb), _Sb((const char *)_S, 0) {}
- istrstream(char *_S, int _N
- :istream(&_Sb), _Sb((const char *)_S, N) {}
- virtual ~istrstream();
- strstreambuf *rdbuf() const
- {return ((strstreambuf *)&_Sb); }
- char *str()
- {return ( _Sb.str()); }
- private:
- strstreambuf _Sb;
- };
- //class ostrstream
- class ostrstream : public ostream {
- public:
- ostrstream( )
- : ostream(&_Sb), Sb() {}
- ostrstream(char *, streamsize, openmode = out);
- virtual ~ostrstream();
- strstreambuf *rdbuf() const
- {return ((strstreambuf *)&_Sb); }
- void freeze(int _F = 1)
- {_Sb.freeze(_F); }
- char *str()
- {return (_Sb.str()); }
- int pcount() const
- {return (_Sb.pcount()); }
- private:
- strstreambuf _Sb;
-
- };
- #endif /* _STRSTREAM_ */
-
-
- Listing 2 The file strstrea.c
- // strstreambuf -- strstreambuf basic members
- #include <limits.h>
- #include <string.h>
- #include <strstream>
-
- strstreambuf::~strstreambuf()
- { // destruct a strstreambuf
- _Tidy();
- }
-
- void strstreambuf::_Init(int n, char *gp, char *pp,
- _Strstate mode)
- { // initialize with possibly static buffer
- streambuf::_Init();
- _Pendsave = 0;
- _Seekhigh = 0;
- _Palloc = 0;
- _Pfree = 0;
- _Strmode = mode;
- if (gp == 0)
- { // make dynamic
- _Alsize = _MINSIZE <= n ? n : _ALSIZE;
- _Strmode = _Dynamic;
- }
- else if (_Strmode & _Dynamic)
- { // initialize a stringbuf from string
- _Alsize = ALSIZE;
- if (0 < n)
- { // copy string
- char *s = new char[n];
- if (S == 0)
- _Nomemory();
- memcpy(s, gp, n);
- _Seekhigh = s + n;
- if (!(_Strmode & _Noread))
- setg(s, s, s + n);
- if (!(_Strmode & _Constant))
- { // make output string and
-
- setp(s, s + n);
- if (!gptr())
- setg(s, s, s);
- }
- _Strmode = _Allocated;
- }
- }
- else
- { // make static
- int size = n < 0 ? INT_MAX : n == 0 ? strlen(gp) : n;
- _Alsize = 0;
- _Seekhigh = gp + size;
- if (pp == 0)
- setg(gp, gp, gp + size);
- else
-
- { // make writable too
- if (pp < gp)
- pp = gp;
- else if (gp + size < pp)
- pp = gp + size;
- setp(pp, gp + size);
- setg(gp, gp, pp);
- }
- }
- }
-
- void strstreambuf::_Tidy()
- { // discard any allocated storage
- if ((_Strmode & (_Allocated) _Frozen)) != _Allocated)
- ;
- else if (_Pfree != 0)
- (*_Pfree)(eback());
- else
- delete [] eback();
- _Seekhigh = 0;
- _Strmode &= ~(_Allocated _Frozen);
- }
-
-
- Listing 3 The file strstpro.c
- // strstpro -- strstreambuf protected members
- #include <string.h>
- #include <strstream>
-
- int strstreambuf::overflow(int ch)
- { // try to extend write area
- if (pptr() != ) && pptr() < epptr())
- return (* _Pn()++ = ch);
- else if (!(_Strmode & _Dynamic)
- _Strmode & (_Constant) _Frozen))
- return (EOF);
- else
- { // okay to extend
- int osize = gptr() == 0 ? 0 : epptr() - eback();
- int nsize = osize + _Alsize;
- char *p = _Palloc != 0 ? (char *)(*_Palloc)(nsize)
- : new char[nsize];
- if (p == 0)
- return (EOF);
- if (0 < osize)
- memcpy(p, eback(), osize);
- else if (_ALSIZE < _Alsize)
- _Alsize = _ALSIZE;
- if (!(_Strmode & -Allocated))
- ;
- else if (_Pfree != 0)
- (*_Pfree)(eback());
- else
- delete [] eback();
- _Strmode =_Allocated;
- if (osize == 0)
- { // setup new buffer
- _Seekhigh = p;
- setp(p, p + nsize);
-
- setg(p, p, p);
- }
- else
- { // revise old pointers
- _Seekhigh = _Seekhigh - eback() + p;
- setp(pbase() - eback() + p, pptr() - eback() + p,
- p + nsize);
- if (_Strmode &_Noread)
- setg(p, p, p);
- else
- setg(p, gptr() - eback() + p, pptr() + 1);
- }
- return (ch == EOF ? 0 : (*_Pn()++ = ch));
- }
- }
- int strstreambuf::pbackfail(int ch)
- { // try to putback a character
- if (gptr() == 0 gptr() <= eback()
- ch != EOF && (unsigned char)ch != _Gn()[-1]
- &&_Strmode &_Constant)
- return (EOF);
- else
- { // safe to back up
- gbump(-1);
- return (ch == EOF ? 0: (*_Gn() = ch));
- }
- }
-
- int strstreambuf::underflow()
- { // read only if read position available
- if (gptr() == 0)
- return (EOF);
- else if (gptr() < egptr())
- return (*_Gn());
- else if (_Strmode & _Noread pptr() == 0
- pptr() <= gptr() &&_Seekhigh <= gptr())
- return (EOF);
- else
- { // update_Seekhigh and expand read region
- if (_Seekhigh < pptr())
- _Seekhigh = pptr();
- setg(eback(), gptr(),_Seekhigh);
- return (*_Gn());
- }
- }
-
-
- Listing 4 The file strstfre.c
- // strstfreeze -- strstreambuf::freeze(bool)
- #include <strstream>
-
- void strstreambuf::freeze(bool freezeit)
- { // freeze a dynamic string
- if (freezeit && !(_Strmode & _Frozen))
- { // disable writing
- _Strmode = _Frozen;
- _Pendsave = epptr();
- setp(pbase(), pptr(), eback());
- }
-
- else if (!freezeit && _Strmode & _Frozen)
- { // re-enable writing
- _Strmode &= ~_Frozen;
- setp(pbase(), pptr(), _Pendsave);
- }
- }
-
-
- Listing 5 The file strstpos.c
- // strstpos -- strstreambuf positioning members
- #include <strstream>
-
- streampos strstreambuf::seekoff(streamoff off,
- ios::seekdir way, ios::openmode which)
- { // seek by specified offset
- if (pptr() != 0 && _Seekhigh < pptr())
- _Seekhigh = pptr();
- if (which & ios::in && gptr() != 0)
- { // set input (and maybe output) pointer
- if (way == ios::end)
- off += _Seekhigh - eback();
- else if (way == ios::cur && !(which & ios::out))
- off += gptr() - eback();
- else if (way != ios::beg off == BADOFF)
- off= _BADOFF;
- if (0 <= off && off <= _Seekhigh - eback())
- { // set one or two pointers
- gbump(eback() - gptr() + off);
- if (which & ios::out && pptr() != 0)
- setp(pbase(), gptr(), epptr());
- }
- else
- off = _BADOFF;
- }
- else if (which & ios::out && pptr() != 0)
- { // set only output pointer
- if (way == ios::end)
- off += _Seekhigh - eback();
- else if (way == ios::cur)
- off += pptr() - eback();
- else if (way != ios::beg off == _BADOFF)
- off= _BADOFF;
- if (0 <= off && off <= _Seekhigh - eback())
- pbump(eback() - pptr() + off);
- else
- off = _BADOFF;
- }
- else // nothing to set
- off = _BADOFF;
- return (streampos(off));
- }
- streampos strstreambuf::seekpos(streampos sp,
- ios::openmode which)
- { // seek to memorized position
- streamoff off = sp.offset();
- if (pptr() != 0 &&_Seekhigh < pptr())
- _Seekhigh = pptr();
- if (off == _BADOFF)
- ;
-
- else if (which & ios::in && gptr() != 0)
- { // set input (and maybe output) pointer
- if (0 <= off && off <= _Seekhigh - eback())
- { // set valid offset
- gbump(eback() - gptr() + off);
- if (which & ios::out && pptr() != 0)
- setp(pbase(), gptr(), epptr());
- }
- else
- off = _BADOFF;
- }
- else if (which & ios::out && pptr() != 0)
- { // set output pointer
- if (0 <= off && off <= _Seekhigh - eback())
- pbump(eback() - pptr() + off);
- else
- off = _BADOFF;
- }
- else // nothing to set
- off = _BADOFF;
- return (streampos(off));
- }
-
-
- Listing 6 The file istrstre.c
- // istrstream -- istrstream basic members
- #include <strstream>
-
- istrstream::~istrstream()
- { // destruct an istrstream
- }
-
-
- Listing 7 The file ostrstre.c
- // ostrstream -- ostrstream basic members
- #include <string.h>
- #include <strstream>
- _STD_BEGIN
-
- ostrstream::ostrstream(char *s, int n, openmode mode)
- : ios(&_Sb), ostream(&_Sb),
- _Sb(s, n, s == 0 !(mode & app) ? s : s + strlen(s))
- { // write at terminating null (if there)
- }
-
- ostrstream::~ostrstream()
- { // destruct an ostrstream
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Questions and Answers
-
-
- qsort and Static Functions
-
-
-
-
- Kenneth Pugh
-
-
- Kenneth Pugh, a principal in Pugh-Killeen Associates, teaches C and C++
- language courses for corporations. He is the author of All On C, C for COBOL
- Programmers, and UNIX for MS-DOS Users, and was a member of the ANSI C
- committee. He also does custom C/C++ programming and provides
- SystemArchitectonicssm services. He is president of the Independent Computer
- Consultants Association. His address is 4201 University Dr., Suite 102,
- Durham, NC 27707, You may fax questions for Ken to (919) 489-5239. Ken also
- receives email at kpugh@allen.com (Internet) and on Compuserve 70125,1142.
-
-
- Q
- In your column in the May 1994 issue of CUJ (which, by the way, arrived here a
- week or so after the July issue -- apparently the boat carrying May's issue to
- the Antipodes took a lengthy detour), you discuss a problem in using the qsort
- function to sort data within a C++ class. You describe a Catch-22 situation,
- in which the comparison function passed to qsort must be a member function if
- it is to access private data members of the class, but qsort is unable to call
- a C++ function because its calling convention differs from that of a C
- function.
- There is a way to circumvent this problem, which works with all the C++
- compilers I have tested (Borland C/C++ 3.1, Microsoft C/C++ 7.0, and GNU g++
- 1.39.1). The trick is to declare the comparison function to be a static member
- function (See Listing 1). Static member functions don't receive a this
- argument and (at least in these compilers) use the same calling conventions as
- ordinary C functions. Unfortunately, however, I suspect there is no guarantee
- that this technique will work with all C++ compilers. Does the proposed C++
- standard have anything to say about this? Perhaps the standards committee
- might consider requiring static member functions to use the same calling
- conventions as C functions, as it would facilitate use of the standard qsort
- and bsearch C library functions.
- By the way, the program listing which accompanied this discussion in the May
- issue contained at least two errors, and also raised a couple of questions in
- my mind. First the (minor) errors: 1) The declaration of class A lacks a
- terminating semicolon. 2) The use of compare_function in the sort must be
- preceded by a prototype. I have some more questions on Listing 1 (reproduced
- here -- see Listing 1)
- 1) Should the declarations of first and second in compare_function be of const
- DataType * or const A::DataType *? My guess would have been the latter, as
- DataType is a private member of class A, and really shouldn't be "visible" to
- an external function.
- 2) When the latter form is used, the Borland compiler compiles it without
- error; the Microsoft compiler fails with the error message: "'DataType' :
- cannot access 'private' member declared in class 'A'." Which is the correct
- behavior?
- Listing 2 is a program which demonstrates my approach of declaring the
- comparison function to be a static member function.
- I do enjoy reading your columns. Keep up the good work!
- Cheers,
- Eric Zurcher
- Canberra, Australia
- A
- For the purpose of implementation hiding, a static function would be better
- than a friend function, if it were guaranteed to work on all compilers. For
- the compilers you listed, static C++ functions do appear to have the same
- calling sequence as C functions. Other readers have responded with similar
- statements about these compilers. I do not believe this feature is in the
- standard.
- Thank you for the sharp-eyed corrections to my program. My grammar checker
- does not look for a terminating ';'. In response to your first question, your
- comparison function should use:
- const A::DataType *first = (A::DataType*) a;
- const A::DataType *second = (A::DataType*) b;
- This is explained in detail on pages 186-187 of The Annotated C++ Reference
- Manual (ARM) by Ellis and Stroustrop [1]. For those who do not have that
- reference handy, I'll paraphrase the ARM's explanation. The rules on nested
- classes have flip-flopped in recent years.
- Class names (as well as structure names) now follow the same rules of scoping
- as other names. In C, structures did not have scoping rules and therefore you
- could use these declarations:
- const DataType *first = (DataType*) a;
- const DataType *second = (DataType*) b;
- The declaration of a structure template inside another structure template was
- just a convenience and had no scope meaning.
- In C++, using a nested class in a non-class function puts it in the scope of
- that class. A program requires scoping information if a reference appears
- outside of that class (i.e., its template and its member functions). This
- scope applies to all nested types. For example, you commonly see:
- class A
- {
- public:
- enum Enumeration {One, Two,
- Three};
- //...
- };
- and a non-member function as:
- void non_member_function()
- {
- A:Enumeration a;
- //. . .
- };
- Access is a separate issue from scope. In answer to your second question,
- access control applies to both members (function and data) and nested types
- (ARM, p. 239). Therefore the attempt to access Datatype by the comparison
- function should not compile.
- Your second program is a bit cleaner on the access side. It eliminates the
- access problems of the first example, at the expense of a slightly more
- complex looking class interface. I will be glad when the new namespace
- mechanism becomes commonplace. Then we will have the same access protection as
- nested classes, but without the visual clutter.
- Reference
- [1] Margaret A. Ellis and Bjarne Stroustrup. The Annotated C++ Reference
- Manual (Addison-Wesley, 1994).
-
- Listing 1 An attempt to use qsort in C++
- #include <stdlib. h>
-
-
- int compare_function(const void * a, const void * b);
-
- class A
- {
- private:
- struct DataType
- {
- double x,y;
- } * data;
- int size;
- public:
- A();
- A(int n, double *x, double *y);
- void sort();
- };
-
- void A::sort()
- {
- qsort (data, size, sizeof(DataType), compare_function);
- }
-
- int compare_function(const void * a, const void * b)
- {
- const DataType *first = (DataType*) a;
- const DataType *second = (DataType*) b;
- // Ought these declaration be as follows ?
- // const A::DataType *first = (A::DataType*) a;
- // const A::DataType *second = (A::DataType*) b;
- if (first->x > second->x)
- return 1;
- else if (first->x < second->x)
- return -1;
- else
- return 0;
- }
- /* End of File */
-
-
- Listing 2 using qsort in C++ made possible via static member function
- #include <stdlib.h>
- #include <iostream.h>
-
- class A
- {
- private:
- struct DataType
- {
- double x,y;
- static int compare_function(const void * a,
- const void * b)
- {
- const DataType *first = (DataType*) a;
- const DataType *second: (DataType*) b;
- if (first->x > second->x)
- return 1;
- else if (first->x < second->x)
- return -1;
- else
-
-
- return 0;
- }
- } * data;
- int size;
- public:
- A(int n, double * x, double * y);
- ~A();
- void sort();
- void list();
- };
-
- A::A(int n, double * x, double * y)
- {
- size: n;
- data = new DataType[size];
- for (int i = 0; i < size; i++)
- {
- data[i].x = x[i];
- data[i].y = y[i];
- }
- };
-
-
- A::~A()
- {
- delete [] data;
- }
-
- void A::sort()
- {
- qsort (data, size, sizeof(DataType),
- DataType::compare_function);
- }
-
- void A::list()
- {
- for (int i = 0; i < size; i++)
- {
- cout << data[i].x << " " << data[i].y << endl;
- }
- }
-
- main()
- {
- double xarray[5] = {4.0, 2.0, 3.0, 5.0, 1.0};
- double yarray[5] = {0.1, 0.2, 0.3, 0.4, 0.5}:
- A AInst (3, xarray, yarray);
- cout << "Original list" << endl;
- AInst.list();
- AInst.sort();
- cout << endl << "Sorted list" << endl;
- AInst.list();
- return 0;
- }
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Code Capsules
-
-
- The Standard C Library, Part 2
-
-
-
-
- Chuck Allison
-
-
- Chuck Allison is a regular columnist with CUJ and a Senior Software Engineer
- in the Information and Communication Systems Department of the Church of Jesus
- Christ of Latter Day Saints in Salt Lake City. He has a B.S. and M.S. in
- mathematics, has been programming since 1975, and has been teaching and
- developing in C since 1984. His current interest is object-oriented technology
- and education. He is a member of X3J16, the ANSI C++ Standards Committee.
- Chuck can be reached on the Internet at 72640.1507@compuserve.com.
-
-
- Last month I divided the fifteen headers of the Standard C Library into three
- Groups, each representing different levels of mastery (see Table 1 through
- Table 3). I continue this month by exploring Group II.
-
-
- Group II: For the "Polished" C Programmer
-
-
-
-
- <assert.h>
-
-
- Well-organized programs provide key points where you can make assertions, such
- as "the index points to the next open array element." It is important to test
- these assertions during development and to document them for the maintenance
- programmer (which, of course, is often yourself). ANSI C provides the assert
- macro for this purpose. You could represent the assertion above, for example,
- as
- #include <assert.h>
- . . .
- assert(nitems < MAXITEMS && i
- == nitems);
- . . .
- If the condition holds, all is well and execution continues. Otherwise, assert
- prints a message containing the condition, the file name, and line number, and
- then calls abort to terminate the program.
- Use assert to validate the internal logic of your program. If a certain thread
- of execution is supposed to be impossible, say so with the call assert(0), as
- in:
- switch (color)
- {
- case RED:
- . . .
- case BLUE:
- . . .
- case GREEN:
- . . .
- default:
- assert(0);
- }
- assert is also handy for validating parameters. A function that takes a string
- argument, for example, could do the following:
- char * f(char *s)
- {
- assert(s);
- . . .
- }
- Assertions are for logic errors, of course, not for run-time errors. A logic
- error is one you could have avoided by correct design. For example, no action
- on the user's part should be able to create a null pointer -- that's clearly
- your fault, so it is appropriate to use assert in such cases. On the other
- hand, a run-time error, such as a memory failure, requires more bulletproof
- exception handling.
- When your code is ready for production, you should have caught all the bugs,
- so you should turn off assertion processing. To do so, you can either include
- the statement
- #define NDEBUG
- in the beginning of the code, or define the macro on the command line if your
- compiler allows it (most use the -D switch). With NDEBUG defined, all
- assertions expand to a null macro, but the text remains in the code for
- documentation.
-
-
- <limits.h>
-
-
-
- By definition, portable programs do not depend in any way on the particulars
- of any one environment. Even assuming that all bytes consist of eight bits is
- not safe. The header <limits.h> defines the upper and lower bounds for all
- integer types (see Table 4). The program in Listing 1 toggles each bit in an
- integer on and off. It uses the value CHAR_BIT, defined in <limits.h>, as the
- number of bits in a byte, to determine the number of bits in an integer. As
- Listing 2 illustrates, you can also use <limits.h> to determine the most
- efficient data type to use for signed numeric values that must span a certain
- range.
-
-
- <stddef.h>
-
-
- The header <stddef.h> defines three type synonyms and two macros (see Table
- 5). When you subtract two pointers which refer to elements of the same array
- (or one position past the end of the array), you get back the difference of
- the two corresponding subscripts, which will be the number of elements between
- the pointers. The type of the result is either an int or a long, whichever is
- appropriate for your memory model. <stddef.h> defines the appropriate type as
- ptrdiff_t.
- The sizeof operator returns a value of type size_t. size_t is the unsigned
- integer type that can represent the size of the largest data object you can
- declare in your environment. Usually an unsigned int or unsigned long is
- sufficient to represent this size. size_t is usually the unsigned counterpart
- of the type used for ptrdiff_t. If you look through the headers in the
- Standard C Library, you'll find extensive use of type size_t. It is good idea
- to use size_t for all array indices and for pointer arithmetic (i.e., adding
- an offset to a pointer), unless for some reason you need the ability to count
- down past zero, which unsigned integers can't do.
- The type wchar_t holds a wide character, an implementation- defined integral
- type for representing characters beyond standard ASCII. You define wide
- character constants with a preceding L, as in:
- #include <stddef.h>
- wchar_t c = L'a';
- wchar_t *s = L"abcde";
- As Listing 3 illustrates, my environment defines a wide character as a
- two-byte integer. This coincides nicely with the emerging 16-bit Unicode
- standard for international characters (see the sidebar "Character Sets"). The
- <stdlib.h> functions listed in Table 6 use type wchar_t. Amendment 1, an
- official addendum to Standard C accepted in 1994, defines many additional
- functions for handling wide and multi-byte characters. For more detailed
- information, see P. J. Plauger's columns in the April 1993 and May 1993 issues
- of CUJ.
- The NULL macro is the universal zero-pointer constant, defined as one of 0,
- 0L, or (void *) 0. It is almost always a bad idea to assume any one of these
- definitions in a program -- for safety, just include a header that defines
- NULL (stddef.h, stdio.h, stdlib.h, string.h, locale.h) and let the system
- figure out the correct representation. <stddef.h> is handy when you need only
- NULL defined in a translation unit and nothing else.
- The offsetof macro returns the offset in bytes from the beginning of a
- structure to one of its members. Due to address alignment contraints, some
- implementations insert unused bytes after members in a structure, so you can't
- assume that the offset of a member is just the sum of the sizes of the members
- that precede it. For example, the program in Listing 4 exposes a one-byte gap
- in the Person structure after the name member, allowing the age member to
- start on a word boundary (a word is two bytes here). Use offsetof if you need
- an explicit pointer to a structure member:
- struct Person p;
- int *age_p;
- age_p = (int*) ((char*)&p
- + offsetof(struct Person, age));
-
-
- <time.h>
-
-
- Most environments provide some mechanism for keeping time. time.h provides the
- type clock_t, a numeric type that tracks processor time (see Table 7). The
- clock function returns an implementation-defined value of type clock_t that
- represents the current processor time. Unfortunately, what is meant by
- "processor time" varies across platforms, so clock by itself isn't very
- useful. You can, however, compare processor times, and then divide by the
- constant CLOCKS_PER_SEC, thus rendering the number of seconds elapsed between
- two points in time. The program in Listing 5 thru Listing 7 uses clock to
- implement such stopwatch functions.
- The rest of the functions in <time.h> deal with calendar time. The time
- function returns a system-dependent encoding of the current date and time as
- type time_t (usually a long). The function localtime decodes a time_t into a
- struct tm (see Listing 8). The asctime function returns a text representation
- of a decoded time in a standard format, namely
- Mon Nov 28 14:59:03 1994
- For more detail, see the Code Capsule "Time and Date Processing in C," CUJ,
- January 1993.
- I'll conclude this series on the Standard C Library next month with a
- discussion of the functionality of the headers in Group III.
- Character Sets
- A script is a set of symbols used to convey textual information. There are
- over 30 major scripts in the world. Some scripts, such as Roman and Cyrillic,
- serve many languages. World scripts can be categorized according to the
- hierarchy in Figure 1.
- Most scripts are alphabetic. The Han script used by Chinese, Japanese, and
- Korean, however, is an ideographic (or more accurately, logographic) script.
- Each Han character represents an object or concept -- these languages have no
- notion of words composed of letters from an alphabet.
- A character set is a collection of text symbols with an associated numerical
- encoding. The ASCII character set with which most of us are familiar maps the
- letters and numerals used in our culture to integers in the range [32, 126],
- with special control codes filling out the 7-bit range [0, 127]. As the 'A' in
- the acronym suggests, this is strictly an American standard. Moreover, this
- standard only specifies half of the 256 code points available in a single
- 8-bit byte.
- There are a number of extended ASCII character sets that fill the upper range
- [128, 255] with graphics characters, accented letters, or non-Roman
- characters. Since 256 code points are not enough to cover even the Roman
- alphabets in use today, there are five separate, single-byte standards for
- applications that use Roman characters (see Figure 2).
- The obvious disadvantage of single-byte character sets is the difficulty of
- processing data from distinct regions, such as Greek and Hebrew, in a single
- application. Single-byte encoding is wholly unfit for Chinese, Japanese, and
- Korean, since there are thousands of Han characters.
- One way to increase the number of characters in a single encoding is to map
- characters to more than one byte. A multibyte character set maps a character
- to a variable-length sequence of one or more byte values. In one popular
- encoding scheme, if the most significant bit of a byte is zero, the character
- it represents is standard ASCII; if not, that byte and the next form a 16-bit
- code for a local character.
- Multibyte encodings are storage efficient since they have no unused bytes, but
- they require special algorithms to compute indices into a string, or to find
- string length, since characters are represented as a variable number of bytes.
- To overcome string indexing problems, Standard C defines functions that
- process multibyte characters, and that convert multibyte strings into
- wide-character strings (i.e., strings of wchar_t, usually two-byte
- characters). Unfortunately, these multi-byte and wide-character functions are
- commonly available only on XPG4-compliant UNIX platforms and Japanese
- platforms. The recently approved Amendment 1 to the C Standard defines many
- additional functions for processing seqences of mutli byte and wide
- characters, and should entice U.S. vendors to step out of their cultural
- comfort zone.
-
-
- Code Pages
-
-
- Since standard ASCII consists of only 128 code points, there are 128 more
- waiting to be used in an eight-bit character encoding. It has been common
- practice to populate the upper 128 codes with characters suitable for local
- use. The combination of values 128-255 together with ASCII is called a code
- page under MS-DOS. The default code page for the IBM PC in the United States
- and much of Europe (#437) includes some box-drawing and other graphics
- characters, and Roman characters with diacritical marks. Other MS-DOS code
- pages include:
- 863 Canadian-French
- 850 Multi-Lingual (Latin-1)
- 865 Nordic
- 860 Portuguese
- 852 Slavic (Latin-2)
- Non-U.S. versions of MS-DOS define other code pages. You can switch between
- code pages in MS-DOS applications, but not in U.S. Microsoft Windows (except
- in a DOS window). Only one code page remains active for Windows-hosted
- applications throughout an entire Windows session. Different versions of
- Windows have code pages appropriate for their region. For example, Windows-J
- for Japan uses a code page based on Shift-JIS. Windows 95 (a.k.a. Chicago)
- will support full code-page switching.
- Since code pages use code points in the range [128, 255], it is important to
- avoid depending on or modifying the high-bit value in any byte of your
- program's data. A program that follows this discipline is called 8-bit clean.
-
-
- Character Set Standards
-
-
- Seven-bit ASCII is the world's most widely-used character set. ISO 646 is
- essentially ASCII with a few codes subject to localization. For example, the
- currency symbol, code point 0x24, is '$' only in the United States, and is
- allowed to "float" to adhere to local conventions. ISO 646 is sometimes called
- the portable character set (PCS) and is the standard alphabet for programming
- languages.
- ISO 8859 is a standard that takes advantage of all 256 single-byte code points
- to define nine eight-bit mappings, to nine selected alphabets (see Figure 2).
- Each of these mappings retains ISO 646 as a subset, hence they differ mainly
- in the upper 128 code points. Some of these mappings are the basis for MS-DOS
- code pages.
-
- There is no official ISO standard for multibyte character sets in the Far
- East. However, each region of the Far East has its own local (national)
- standards. PC-industry standards, based on national standards, are also in
- common use in the Far East. Examples include Eten, Big Five, and Shift JIS.
-
-
- ISO 1O646
-
-
- To simplify the development of internationalized applications, ISO developed
- the Universal Multiple-Octet Coded Character Set (ISO 10646), to accommodate
- all characters from all significant modern languages in a single encoding. An
- octet is a contiguous, ordered collection of eight bits, which is a byte on
- most systems. ISO 10646 allows for 2,147,483,648 (231) characters, although
- only 34,168 have been defined. It is organized into 128 groups, each group
- containing 256 planes of 65,536 characters each (256 rows x 256 columns.
- Any one of the 231 characters can be addressed by four octets, representing
- respectively the group, plane, row, and column of its location in the
- four-dimensional space. Consequently, ISO 10646 is a 32-bit character
- encoding. ASCII code points are a subset of ISO 10646 -- you just add leading
- zeroes to fill out 32 bits. For example, the encoding for the letter 'a' is
- 00000061 hexadecimal (i.e., Group 0, Plane 0, Row 0, Column 0x61).
- Plane 0 of Group 0 is the only one of the 32,768 planes that has been
- populated to date. It is called the Basic Multi-Lingual Plane (BMP). ISO 10646
- allows conforming implementations to be BMP-based, i.e., requiring only two
- octets, representing the row and column within the BMP. The full four-octet
- form of encoding is called UCS-4, and the two-octet form UCS-2. Under UCS-2,
- therefore, the hexadecimal encoding for the letter 'a' is 0061 (Row 0, Column
- 0x61). Row 0 of the BMP is essentially ISO 8859-1 (Latin-l) with the U.S.
- dollar sign as the currency symbol.
- ISO 10646 also defines combining characters, such as non-spacing diacritics.
- In conforming applications, combining characters always follow the base
- character that they modify. The UCS-2 encoding for á, then, consists of two
- 16-bit integers: 0061 0301 (0301 is called the non-spacing acute). For
- compatibility with existing character sets, there is also a single UCS-2 code
- point for á (00e1).
- For the most part, only Roman characters have such dual representations. Some
- non-Roman languages, such as Arabic, Hindi, and Thai, also require the use of
- combining characters. ISO-10646 specifies three levels of conformance for
- tools and applications:
- Level 1 combining characters not allowed
- Level 2 combining characters allowed for Arabic, Hebrew, and Indic scripts
- only
- Level 3 combining characters allowed with no restrictions
-
-
- Unicode
-
-
- Unicode is a 16-bit encoding scheme that supports most modern written
- languages. It began independently of ISO 10646, but with Unicode version 1.1,
- it is now a subset of 10646 (to be precise, it is UCS-2, Level 3). Unicode
- also defines mapping tables to translate Unicode characters to and from most
- national and international character set standards.
- Some applications should readily convert to Unicode. Since ASCII is a subset,
- it is only necessary to change narrow (eight-bit) characters to wide
- characters. In C and C++, this means replacing char declarations with wchar_t.
- Some other character sets, such as Thai and Hangul, appear in the same
- relative order within Unicode, so you just need to add or subtract a fixed
- offset. Converting Han characters requires a lookup table.
- Vendors are now beginning to support Unicode, and tools are available at both
- the operating system and API levels. Tools supporting the 32-bit encodings of
- ISO 10646 are not expected for many years -- especially since no planes beyond
- the BMP have been populated.
-
-
- Bibliography
-
-
- "UCS Coexistence/Migration," X/Open Internal Report, Doc. No. SC22/WG20 N252,
- 1993.
- The Unicode Standard. The Unicode Consortium, Addison-Wesley, 1991.
- Katzner, Kenneth. The Languages of the World. 1986.
- Martin, Sandra. "Internationalization Explored," UniForum, 1992.
- Plauger, P. J., "Large Character Sets for C," Dr. Dobb's Journal, August 1992.
- Figure 1 World Scripts
- European
- Armenian, Cyrillic, Georgian, Greek, Roman
- Indic
- Northern
- Bengali, Devanagari, Gujarati, Gurmukhi, Oriya
- Southern
- Kannada (Kanarese), Malayalam, Sinhalese, Tamil,
- Telugu
- Southeast
- Burmese, Khmer, Lao, Thai
- Central Asian
- Tibetan
- Middle Eastern
- Arabic, Hebrew
- East Asian (Oriental)
- Han, Bopomofo, Kana (Hiragana + Katakana), Hangul
- Other Asian
- Lanna Thai, Mangyan, Mongolian, Naxi, Pollard, Pahawh
- Hmong, Tai Lü, Tai Nüa, Yi
- African
- Ethiopian, Osmanya, Tifinagh, Vai
- Native American
- Cree
- Miscellaneous
- Chemistry, Mathematics, Publishing Symbols, IPA
- (International Phonetic Alphabet)
-
- Figure 2 Eight-bit Character Set Standards
- ISO 8859-1 (Latin-1) Western European
- ISO 8859-2 (Latin-2) Eastern European
- ISO 8859-3 (Latin-3) Southeastern European
- ISO 8859-4 (Latin-4) Northern European
- ISO 8859-5 Cyrillic
- ISO 8859-6 Arabic
- ISO 8859-7 Greek
- ISO 8859-8 Hebrew
- ISO 8859-9 (Latin-5) Western European + Turkish
- Table 1 Standard C Headers: Group 1 (required knowledge for every C
- programmer)
- <ctype.h> Character Handling
- <stdio.h> Input/Output
- <stdlib.h> Miscellaneous Utilities
- <string.h> Text Processing
- Table 2 Standard C Headers: Group II (tools for the professional)
- <assert.h> Assertion Support for
- Defensive Programming
- <limits.h> System Parameters for
- Integer Arithmetic
- <stddef.h> Universal Types &
- Constants
- <time.h> Time Processing
- Table 3 Standard C Headers: Group III (power at your fingertips when you need
- it)
- <errno.h> Error Detection
- <float.h> System Parameters for
- Real Arithmetic
- <locale.h> Cultural Adaptation
- <math.h> Mathematical Functions
- <setjmp.h> Non-local Branching
- <signal.h> Interrupt Handling (sort of)
- <stdarg.h> Variable-length Argument
- Lists
- Table 4 Interger boundaries defined in <limits.h>
- CHAR_BIT 8
- SCHAR_MIN -127
- SCHAR_MAX 127
- UCHAR_MAX 255
-
- [if char == signed char]
- CHAR_MIN SCHAR_MIN
- CHAR_MAX SCHAR_MAX
- [else]
- CHAR_MIN 0
- CHAR_MAX UCHAR_MAX
-
- MB_LEN_MAX 1
-
- SHRT_MIN -32767
- SHRT_MAX 32767
- USHRT_MAX 65535
-
- INT_MIN -32767
- INT_MAX 32767
- UINT_MAX 65535
-
- LONG_MIN -2147483647
- LONG_MAX 2147483647
- ULONG_MAX 4294967295
-
- Table 5 Definitions in <stddef.h>
- ptrdiff_t type for pointer subtraction
- size_t type for sizeof
- wchar_t wide-character type
- NULL zero pointer
- offsetof offset in bytes of structure members
- Table 6<stdlib.h> functions that use wchar_t
- mbtowc translate multibyte character to wide character
- wctomb translate wide character to multibyte character
- mbstowcs translate multibyte string to wide string
- wcstombs translate wide string to multibyte string
- Table 7 Definitions in <time.h>
- Macros
- ----------------------------------------------------
- NULL
- CLOCKS_PER_SEC
-
- Types
- ----------------------------------------------------
- size_t
- clock_t system clock type
- time_t ncoded time/date value
- struct tm decoded time/date components
-
- Functions
- ----------------------------------------------------
- difftime duration between two times
- mktime normalizes a struct tm
- time retrieves current time encoding
- asctime text representation of a time value
- ctime text representation of current time
- gmtime decodes into UTC time
- localtime decodes a time value
- strftime formats a decoded time
-
- Listing 1 A use for CHAR_BIT
- /* bit3.c: Toggle bits in a word */
- #include <stdio.h>
- #include <limits.h>
-
- #define WORD unsigned int
- #define NBYTES sizeof(WORD)
- #define NBITS (NBYTES * CHAR_BIT)
- #define NXDIGITS (NBYTES * 2)
-
- main()
- {
- WORD n: 0;
- int i, j;
-
- for (j = 0; j < 2; ++j)
- for (i = 0; i < NBITS; ++i)
- {
- n ^= (1 << i);
- printf("%0*X\n",NXDIGITS,n);
- }
-
- return 0;
- }
-
-
- /* Output:
- 0001
- 0003
- 0007
- 000F
- 001F
- 003F
- 007F
- 00FF
- 01FF
- 03FF
- 07FF
- 0FFF
- 1FFF
- 3FFF
- 7FFF
- FFFF
- FFFE
- FFFC
- FFF8
- FFF0
- FFE0
- FFC0
- FF80
- FF00
- FE00
- FC00
- F800
- F000
- E000
- C000
- 8000
- 0000
- */
-
- /* End of File */
-
-
- Listing 2 Uses <limits.h> to choose a suitable numeric type
- /* range.c */
- #include <stdio.h>
- #include <limits.h>
-
- #define LOWER_BOUND <your min here>
- #define UPPER_BOUND <your max here>
-
- /* Determine minimal numeric type for range */
- #if LOWER_BOUND < LONG_MIN LONG_MAX < UPPER_BOUND
- typedef double Num_t;
- #elif LOWER_BOUND < INT_MIN INT_MAX < UPPER_BOUND
- typedef long Num_t;
- #elif LOWER_BOUND < SCHAR_MIN SCHAR_MAX < UPPER_BOUND
- typedef int Num_t;
- #else
- typedef signed char Num_t;
- #endif
-
- main()
-
- {
- Num_t x;
-
- printf("sizeof(Num_t) == %d\n",sizeof x);
- return 0;
- }
-
- /* End of File */
-
-
- Listing 3 Illustrates wide character strings
- #include <stddef.h>
- #include <stdio.h>
-
- main()
- {
- char str[] = "hello";
- wchar_t wcs[] = L"hello";
-
- printf("sizeof str = %d\n",sizeof str);
- printf("sizeof wcs = %d\n",sizeof wcs);
- return 0;
- }
-
- /* Output:
- sizeof str = 6
- sizeof wcs = 12
- */
- /* End of File */
-
-
- Listing 4 offsetof exposes alignment within a structure
- #include <stddef.h>
- #include <stdio.h>
-
- struct Person
- {
- char name[15];
- int age;
- };
-
- main()
- {
- printf("%d\n",offsetof(struct Person, age));
- return 0;
- }
-
- /* Output:
- 16
- /*
-
- / End of File */
-
-
- Listing 5 Stopwatch Function Prototypes
- /* timer.h: Stopwatch Functions */
-
- void timer_reset(void);
- void timer_wait(double nsecs);
-
- double timer_elapsed(void);
-
- /* End of File */
-
-
- Listing 6 Implementation of Stopwatch Functions
- /* timer.c: Stopwatch Functions */
-
- #include <time.h>
- #include "timer.h"
-
- static clock_t start = (clock_t) 0;
-
- /* Reset the timer */
- void timer_reset(void)
- {
- start = clock();
- }
-
- /* Wait a number of seconds */
- void timer_wait(double secs)
- {
- clock_t stop = clock() +
- (clock_t) (secs* CLOCKS_PER_SEC);
- while (clock() < stop);
- ;
- }
-
- /* Compute elapsed time in seconds */
- double timer_elapsed(void)
- {
- return (double)(clock() - start) / CLOCKS_PER_SEC;
- }
-
- /* End of File */
-
-
- Listing 7 Illustrates the stopwatch functions
- /* t_timer.c: Tests the stopwatch functions */
-
- #include <stdio.h>
- #include <limits.h>
- #include "timer.h"
-
- main()
- {
-
- long i;
-
- timer_reset();
-
- /* Delay */
- for (i = 0; i < LONG_MAX; ++i)
- ;
-
- /* Get elapsed time */
- printf("elapsed time: %lf secs\n", timer_elapsed());
- return 0;
- }
-
-
- /* Output:
- elapsed time: 565.070000 secs
- */
-
- /* End of File */
-
-
- Listing 8 The definition of struct tm from <time.h>
- struct tm
- {
- int tm_sec; /* seconds (0 - 59) */
- int tm_min; /* minutes (0 - 59) */
- int tm_hour; /* hours (0 - 23) */
- int tm_mday; /* day of month (1 - 31) */
- int tm_mon; /* month (0 - 11) */
- int tm_year; /* years since 1900 */
- int tm_wday; /* day of week (0 - 6) */
- int tm_yday; /* day of year (0 - 365) */
- int tm_isdst; /* daylight savings flag */
- };
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Stepping Up To C++
-
-
- Minor Enhancements to C++ as of CD Registration
-
-
-
-
- Dan Saks
-
-
- Dan Saks is the president of Saks & Associates, which offers consulting and
- training in C++ and C. He is secretary of the ANSI and ISO C++ committees. Dan
- is coauthor of C++ Programming Guidelines, and codeveloper of the Plum Hall
- Validation Suite for C++ (both with Thomas Plum). You can reach him at 393
- Leander Dr., Springfield OH, 45504-4906, by phone at (513)324-3601, or
- electronically at dsaks@wittenberg.edu.
-
-
- In July, the ANSI and ISO C++ standards committees voted to submit their
- working draft of the C++ standard for registration as a CD (committee draft).
- This is the second installment in a series that summarizes the state of the
- C++ language as of that milestone.
- Since adopting the ARM [1] as the base document for the standard, the
- committees have done a lot more than just clarify the C++ language definition;
- they have been changing existing features and adding new ones at a fairly
- steady clip. CD registration represents, among other things, a tacit statement
- from the committees that they're more or less done adding new stuff. The words
- in the draft are not yet as clear or as precise as they will be when the
- standard is final, but at least now the draft describes most, if not all, of
- the language features we should expect to see in that eventual C++ standard.
- Last month, I listed all of the ways the C++ language described by the draft
- standard differs from the dialect described in the ARM. (See "Stepping Up to
- C++: C++ at CD Registration," CUJ, January 1995.) My list included only
- substantive changes (changes in the C++ language itself) and omitted changes
- that are essentially editorial (changes in the description of C++, not in C++
- itself).
- I classified the substantive changes into four broad categories:
- major extensions that dramatically increase the complexity of the language and
- support alternative programming styles and paradigms
- minor enhancements that extend the language in less dramatic ways
- changes that alter the meaning of existing features
- clarifications of existing features
- I also provided brief explanations of each of the major extensions:
- templates
- exception handling
- run-time type information (including dynamic_cast)
- namespaces
- along with pointers to other sources of information about these features.
- This month, I continue in the same vein with explanations of the minor
- enhancements. Some of these review features I described in earlier columns.
- (See "Stepping Up To C++: Recent Extensions to C++," CUJ, June, 1993, and
- "Stepping Up To C++: The Return Type of Virtual Functions," CUJ, March, 1994.)
- Stroustrup [2] offers more insights and examples on most of these features.
-
-
- New Keywords and Digraphs
-
-
- C++, like C, requires a character set based on the Roman alphabet. In
- particular, C++ uses the English alphabetic characters 'A' through 'Z' and 'a'
- through 'z', along with a handful of arithmetic operators and punctuation
- symbols. Unfortunately, many other natural languages based on the Roman
- alphabet use more than just the 26 (times two) characters in the English
- alphabet.
- Computer systems that support non-English alphabets usually make room for
- native language characters and punctuation by omitting other punctuation
- characters normally found on American English systems. For example, Danish
- keyboards, displays, and printers replace the [ and ] with Æ and Å,
- respectively. A subscript expression like a[i] comes out on a Danish display
- device looking like aÆiÅ. I can imagine that this takes some of the fun out of
- programming in C and C++.
- ISO646 is the international standard 7-bit character set. It specifies the set
- of characters common to all systems based on the Roman alphabet. ISO646 does
- not use every available 7-bit code. Each nationality can define its own
- variant of ISO646 that uses the unused codes for native language characters or
- symbols. ASCII, the US national variant of ISO646, uses the available codes
- for symbols like {, }, [, ], /, ^, \, and ~ (all of which are used in C and
- C++). Other national variants assign these same codes to other alphabetic
- characters, like à or ü, or to punctuation like ¿.
- A current trend in international standards is to augment programming languages
- so that programmers can work without too much inconvenience using only the
- invariant set of ISO646 characters. As part of this trend, C++ includes new
- keywords and digraphs (two-character symbols) as alternate ISO646-compliant
- spellings for the troublesome operators. These keywords and digraphs appear in
- Table 1. Using the digraphs, you can write the subscripting expression a[i] as
- a<:i:>.
- ISO C provides the same alternate spellings; however, C specifies the
- identifiers in Table 1 as macros defined in a standard header <iso646.h>.
- Thus, you can continue using those identifiers as user-defined identifiers in
- C as long as you don't include <iso646.h>. However, C++ treats these
- identifiers as reserved words; you cannot use them as user-defined identifiers
- in any C++ program. The C++ library provides a header <iso646.h> (which may be
- empty) just for compatibility with C.
-
-
- Operator Overloading on Enumerations
-
-
- At one time, enumerations were integral types (as they are in C), but those
- days are gone. The current draft says that each distinct enumeration
- constitutes a different enumerated type. Enumerations are not integral, but
- they do promote to int, unsigned int, long, or unsigned long.
- For example, given
- enum day { SUN, MON, ..., SAT };
- a valid C declaration like
- enum day d = 0;
- is no longer valid in C++, because C++ won't convert 0 (an int) to day (an
- enumeration) without a cast. However,
- int n = SUN;
- is still valid in C++, as it is in C, because SUN (a constant of type day)
- still promotes to int.
- C++ does not allow operator overloading on primitive types, such as int. When
- enumerations were integral types, C++ could not allow operator overloading on
- enumerations either, without severely complicating the rules for overload
- resolution. Once enumerations became distinct types, allowing overloading on
- enumerations became a relatively small change.
- Removing enumerations from the integral types introduced a potentially serious
- incompatibility with C, namely, that various built-in arithmetic operators,
- such as ++, --, += and -=, no longer apply to enumerations. Therefore, a loop
- such as
- for (d = SUN; d <= SAT; ++d)
- no longer compiles as C++ when ++ is the predefined operator. However, with
- operator overloading on enumerations, you can define
- inline day &operator++(day &d)
-
- {
- return d = day(d + 1);
- }
- as the prefix form of ++ for objects of type day. With this function
- definition present, the for statement will compile.
-
-
- operator new[] and operator delete[]
-
-
- A new expression acquires storage by calling an allocation function. The
- standard C++ library provides a default allocation function, declared as
- void *operator new(size_t n);
- An expression such as
- px = new X;
- loosely translates into code of the form
- px = (X *)operator new(sizeof(X));
- px->X(); // apply constructor
- A delete expression releases storage by calling a deallocation function. The
- default deallocation function declared in the library has the form
- void operator delete(void *p);
- An expression such as
- delete px;
- loosely translates into code of the form
- px->~X(); // apply destructor
- operator delete(px);
- The default allocation and deallocation functions are very general, and may
- waste too much time or storage in some applications. C++ lets you write you
- own versions of operator new and operator delete. If you define your own
- operator new, every new expression in your program will call your allocator
- instead of the default supplied in the library. Ditto for operator delete and
- delete expressions.
- Writing replacements for the global allocator and deallocator can be a big
- chore, and it's usually unnecessary. Often all you need to do is tune the
- memory allocator for objects of a few particular classes. C++ lets you define
- a different allocator and deallocator for each class. For example,
- class node
- {
- public:
- void *operator new(size_t);
- void operator delete(void *p);
- ...
- };
- defines class node with its own class-specific versions of new and delete. A
- call such as
- p = new node;
- allocates memory using node::operator new, rather than the global operator
- new. Similarly,
- delete p;
- deallocates memory using node::operator delete. new and delete expressions for
- objects of built-in types, and for class types that do not declare their own
- operator new and operator delete, continue to use the global allocation and
- deallocation functions.
- According to the ARM, allocating an array of X objects always uses the global
- operator new, even if X is a class that defines its own operator new. That is,
- px = new X[n];
- ignores X::operator new and uses ::operator new. By the same token, deleting
- that array using
- delete [] px;
- ignores X::operator delete and uses ::operator delete.
- The current C++ draft standard gives programmers greater control over dynamic
- memory management for arrays of objects. Now, a new expression such as
- px = new X[n];
- invokes an allocation function declared as
- void *operator new[](size_t n);
- Similarly,
- delete [] px;
- invokes a deallocation function declared as
- void operator delete[](void *p);
- The standard library provides default implementations for operator new[] and
- operator delete[], which you can replace at your pleasure. You can even define
- operators new[] and delete[] for individual classes, as in
- class node
- {
- public:
- void *operator new(size_t);
- void *operator new[](size_t);
- void operator delete(void *p);
- void operator delete[](void *p);
- ...
-
- };
- so that
- p = new node[n];
- invokes node::operator new[] instead of node::operator new.
-
-
- Relaxed Virtual Function Return Typ es
-
-
- Quoth the ARM: "It is an error for a derived class function to differ from a
- base class virtual function in the return type only." For example, in
- class B
- {
- public:
- virtual int vf(int);
- ...};
- class D : public B
- {
- ...
- void vf(int); // error
- ...
- };
- the declaration of D::vf is an error because it has the same name and
- signature (parameter types) as a function declared in base class B, but they
- have different return types. Most of the time, this is a reasonable
- restriction. Occasionally it isn't. Here's one such occasion.
- Some applications need to be able to clone (create an exact copy of) an
- object. Typically, a cloning function for a class X looks something like
- X *X::clone() const
- {
- return new X(*this);
- }
- The expression new X(*this) creates a new dynamically-allocated X object by
- copying the object addressed by this.
- When used in a class that's the root of a polymorphic hierarchy, clone
- functions should be virtual (possibly pure), so you can clone an object
- without knowing its exact type. For example, Listing 1 shows a simple
- hierarchy of geometric shapes with a virtual clone function. Using these
- types, a call such as
- shape *cs = s->clone();
- creates a new shape with the same dynamic type as shape s. That is, if at the
- time of the call, s actually points to a circle, then s->clone() returns a
- circle *. If s points to a rectangle, then s->clone() returns a rectangle *.
- Normally, a clone function for a class X should have a return type of X *.
- However, in Listing 1, circle::clone returns a shape *, not a circle *, to
- satisfy the ARM's restriction that an overriding function must return exactly
- the same type as the function it overrides. Unfortunately, satisfying this
- restriction often requires you to use casts in contexts where it seems that
- you should not need them. For example, you cannot write
- rectangle *r;
- ...
- rectangle *cr = r->clone();
- because r->clone() returns a shape *, not a rectangle *. Even though a
- rectangle is a shape, a shape is not necessarily a rectangle. Therefore, you
- must add a cast, as in
- rectangle *cr = (rectangle *)r->clone();
- The current draft relaxes the ARM's original restriction to eliminate the need
- for this cast. You can declare each virtual clone function in the hierarchy so
- that it returns a pointer whose static type is the same as its dynamic type.
- That is, circle::clone can return a circle * and rectangle::clone can return a
- rectangle *, even though the function they override, shape::clone, returns a
- shape *.
- By and large, the ARM's restriction still stands. The current draft simply
- adds two special cases where the return types need not match exactly.
- Specifically, for all classes B and D defined as
- class B
- {
- ...
- virtual BT f();
- ...
- };
- class D : public B
- {
- ...
- DT f();
- ...
- };
- types BT and DT must be identical, or they must satisfy either of the
- following conditions:
- 1. BT is BB * and DT is DD *, where DD is derived from BB.
- 2. BT is BB & and DT is DD &, where DD is derived from BB.
- In either case (1) or (2),
- 3. class D must have the access rights to convert a BB * (or BB &) to a DD *
- (or DD &, respectively).
- In most applications with hierarchies like this, BB is a public base class of
- DD, so D can perform the conversions. But if, for example, BB is a private
- base class of DD, then condition (3) does not hold. The above rules apply even
- if D is derived indirectly from B. Furthermore, BB might be B and DD might be
- D, as is the case with clone functions.
- Types BB and DD, above, may include cv-qualifiers (const and volatile). These
- qualifiers in BB and DD need not be identical, as long as every qualifier
- that's part of DD is also part of BB. BB may have qualifiers that are not part
- of DD.
-
-
-
- wchar_t as a Distinct Type
-
-
- Like C, C++ provides wide-character literals and multibyte strings so that
- programmers can manipulate very large character sets, like Japanese Kanji.
- Several headers in the Standard C library define wchar_t as the wide character
- type, an integral type sufficiently large to represent all character codes in
- the largest character set among the supported locales.
- The C++ committees wanted to support input/output for wide characters as well
- as "narrow" characters. For example, for a time they wanted to be able to add
- another output operator
- ostream &operator<<(ostream &os,
- wchar_t w);
- in addition to existing operators, such as
- ostream &operator<<(ostream &os,
- char c);
- ostream &operator<<(ostream &os,
- int i);
- //and many others
- so that given
- int i;
- wchar_t w;
- the expression
- cout << i;
- displays i as an int, and
- cout << w;
- displays w in its proper graphic representation. Unfortunately, this was
- impossible with wchar_t defined by a typedef.
- The problem is that a typedef is not a distinct type; it's an alias for
- another type. If the library defines wchar_t as
- typedef wchar_t int;
- then
- ostream &operator<<(ostream &os,
- int i);
- ostream &operator<<(ostream &os,
- wchar_t w);
- are the same function. That function will (most likely) display objects of
- type wchar_t as numbers.
- Thus, wchar_t is now a keyword in C++ representing a distinct type. wchar_t
- still has the same representation as one of the standard integral types
- (meaning it has the same size, alignment requirements, and "signedness" as one
- of the integral types), but it is now a distinct type for the purposes of
- overload resolution.
-
-
- A Built-in Boolean Type
-
-
- C doesn't have a Boolean type; it simply assumes that a scalar value is
- "false" if it compares equal to zero and "true" if it's non-zero. For the most
- part, the Standard C library uses int as the Boolean type, and many
- programmers follow suit.
- Those of us who truly felt the absence of a Boolean type defined one
- ourselves. Unfortunately, there are an awful lot of different ways to do it.
- Some programmers spell the type as bool. Others use Bool, boolean, Boolean,
- bool_t, or even logical. The type can be char or int, signed or unsigned. The
- definition itself can be a macro
- #define bool int
- #define false 0
- #define true 1
- or a typedef
- typedef unsigned char bool;
- or even an enumeration
- enum bool { false, true };
- Consequently, it's not at all uncommon for the definitions of bool, false, and
- true (however you spell them) in one library to conflict with the definitions
- in another.
- The C++ standards committees decided to eliminate these conflicts by defining
- a standard Boolean type. C++ now has a Boolean type with the following
- properties:
- bool is a keyword designating a signed integral type.
- false and true are keywords designating constants of type bool.
- bool expressions promote to int such that false converts to zero and true
- converts to one.
- int and pointer expressions can convert to bool such that zero converts to
- false and non-zero converts to true.
- In addition:
- The built-in relational operators <, >, ==, !=, <=, and >= yield a bool
- result.
- The built-in logical operators &&, //, and ! take bool operands (or operands
- that automatically convert to bool) and yield a bool result.
- The conditional expression in an if, while, or for statement, or the first
- operand of ?:, must have bool type (or a type that automatically converts to
- bool).
- The committees' intent is that, except for definitions of bool, false, and
- true, code that uses a Boolean type should continue to work as before.
- The committees agreed to preserve, at least for the time being, the sloppy but
- apparently common coding practice of using ++ to set a Boolean value to true.
- C++ currently accepts prefix and postfix ++ as lvalue, of type bool, both of
- which set to true. However, the draft deprecates this feature, meaning it may
- disappear from some future standard.
- By the way, the committees did explore alternatives to defining bool as a
- built-in type. They considered defining it in the library as a typedef, as a
- class, or as an enumeration, but each approach had flaws.
- The problem with a typedef is that it is not a distinct type for overloading.
- If the library defines
-
- typedef int bool;
- then declarations
- void f(int);
- void f(bool);
- are not a pair of overloaded functions; they're two declarations for the same
- function. (This is the same reason why wchar_t is not a typedef.)
- Defining bool as a class type poses a different problem. Given the current
- rules for applying user-defined conversions during argument-matching, defining
- bool as a class might break existing code. In their analysis of the Boolean
- type, Dag Brück and Andrew Koenig used an example similar to the one in
- Listing 2 to illustrate the problem.
- Listing 1 shows a class X with a constructor that accepts a single int
- argument. Thus, that constructor is also a user-defined conversion from int to
- X. In the absence of a bool type, the expression n > 0 yields an int. The call
- f(n > 0) uses the constructor X(int) to convert that int result to X, and then
- passes that X to f(X).
- Now suppose C++ defined bool as a class type with a conversion to int:
- class bool
- {
- public:
- operator int();
- ...
- };
- If relational operators, like >, yield a bool result, then the call f(n > 0)
- in Listing 2 must first convert the result of n > 0 to int using
- bool::operator int, and then convert that int to X using X::X(int).
- Unfortunately, there's already a rule in C++ that argument matching during a
- function call will apply at most one user-defined conversion. When bool is a
- class, this call requires two user-defined conversions: bool::operator int and
- X::X(int). Brück and Koenig reasoned that to avoid breaking this code or
- meddling with the already complicated argument-matching rules, the conversion
- from bool to int cannot be user-defined; it must be built-in.
- When enumerations were still integral types, it might have been possible to
- define bool as an enumeration and preserve the implicit conversions from int
- to bool. Since int values no longer convert quietly to enumerations, it can't
- be done without special rules for bool. But that's the same as making bool a
- built-in type.
-
-
- Declarations in Conditional Expressions
-
-
- The proposal to add run-time type information (RTTI) included an extension to
- allow declarations in conditional expressions. The scope of a name declared in
- a condition is the statement(s) controlled by that condition. For example,
- if (circle *c = dynamic_cast<circle *>(s))
- {
- // c is in scope here...
- }
- // but not here
- The declaration yields the value of the declared object after initialization.
- The syntax for such declarations is extremely limited. (The revised grammar
- appears in Table 2.) The declaration can declare only one object, and it must
- have an initializer with = as the delimiter. For example, C++ will not accept
- if (complex z(r, i))
- You must write the condition as
- if (complex z = complex(r, i))
- Declarations can appear in place of expressions only in the controlling
- expressions of if, switch, for, or while statements. They cannot appear in the
- increment expression of a for statement, in the controlling expression of a
- do-while statement, or in the conditional expression of ?: expression.
- The scope of a name declared in the condition of an if statement includes the
- else part. For example,
- if (int c = getchar())
- {
- // c is non-zero here
- }
- else
- {
- // c is zero here
- }
- //c is undefined here
- Finally, you cannot declare an entity in the outermost block of the statement
- controlled by a condition using the same name as the object declared in the
- condition. For example,
- while (X *p = iter.next())
- {
- char *p; // error, p already declared
- ...
- }
- I'll continue next month with the explanations of the other minor enhancements
- to C++.
- References
- [1] Margaret A. Ellis and Bjarne Stroustrup. The Annotated C++ Reference
- Manual. (Addison-Wesley, 1990).
- [2] Bjarne Stroustrup. The Design and Evolution of C++. (Addison-Wesley,
- 1994).
- Table 1 New keywords and digraphs keywords as alternate ISO646- compliant
- spellings for existing operators.
- Existing Alternate
- Spelling Spelling
- ------------------
- [ <:
- ] :>
-
- { <%
- } %>
- # %:
- ## %:%:
- & bitand
- && and
- bitor
- or
- ^ xor
- ~ compl
- &= and_eq
- = or_eq
- ^= xor_eq
- ! not
- != not_eq
- Table 2 Grammar changes to allow declarations in conditionals
- Old grammars (as in the ARM):
-
- selection-statement:
- if ( expression ) statement
- if ( expression ) statement else statement
- switch ( expression ) statement
-
- iteration-statement:
- while ( expression ) statement
- do statement while ( expression ) ;
- for ( for-init-statement expressionopt ; expressionopt ) statement
-
- New grammar (as in the draft):
-
- condition:
- expression
- type-specifier-seq declarator = assignment-expression
-
- selection-statement:
- if ( condition ) statement
- if ( condition ) statement else statement
- switch ( condition ) statement
-
- iteration-statement:
- while ( condition ) statement
- do statement while ( expression ) ;
- for ( for-init-statement conditionopt ; expressionopt ) statement
-
- Listing 1 A polymorphic hierarchy of shapes with a clone function
- class shape
- {
- public:
- virtual shape *clone() const = 0;
- ...
- };
-
- class circle : public shape
- {
- public:
- shape *clone() const;
- ...
- };
-
-
- shape *circle::clone() const
- {
- return new circle(*this);
- }
-
- class rectangle : public shape
- {
- public:
- shape *clone() const;
- ...
- };
-
- shape *rectangle::clone() const
- {
- return new rectangle(*this);
- }
-
-
- Listing 2 An example that illustrates why bool can't be a class type
- class X
- {
- public:
- X(int);
- ...
- };
-
- void f(X);
-
- int main()
- {
- ...
- f(n >0);
- ...
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- On The Networks
-
-
- Where to Get the Source
-
-
-
-
- Sydney S. Weinstein
-
-
- Sydney S. Weinstein, CDP, CCP is a consultant, columnist, lecturer, author,
- professor, and President of Myxa Corporation, an Open Systems Technology
- company specializing in helping companies move to and work with Open Systems.
- He can be contacted care of Myxa Corporation, 3837 Byron Road, Huntingdon
- Valley, PA 19006-2320, or via electronic mail using the Internet/USENET
- mailbox syd@Myxa.com (dsinc!syd for those that cannot do Internet addressing).
-
-
- This is my fifth anniversary in writing this column, and before I go on to
- some highlights, I want to update you on where to get the sources mentioned in
- this column. One method I can tell you won't work is e-mailing me for it. I
- write these columns based on the CUJ production schedule, which is months
- ahead of when you see them. So the items I write about are long since gone
- from my local disk before you even know I have written about them.
- However, all is not lost. Many Internet sites do archive postings that were
- made to the USENET Network News source groups. And with the increasing
- availability of Internet access, its becoming even easier to reach them.
- First, the quick overview: "On The Networks" covers articles posted to several
- of the source groups on USENET Network News: comp.sources.games,
- comp.sources.misc, comp.sources.reviewed, comp.sources.unix, comp.sources.x,
- and alt.sources. (However, comp.sources.reviewed is a bit quiet of late.) Each
- of these groups is like a section of a large electronic magazine called USENET
- Network News. I call it a magazine, and not a bulletin board, partly because
- of the way its "news" is distributed. Unlike a bulletin board, requiring each
- reader to access a central machine to read the messages, USENET Network News
- arrives on a subscription basis at each participating computer site.
- Since most participating sites keep news articles online for a short period of
- time (usually less than two weeks), by the time a piece of software appears in
- this column it will have been expired and deleted for a long time. Thus it is
- necessary to access a news archive site.
- Many sites around the country have agreed to archive specific news groups.
- These archive sites are listed in the comp.archives.news group. Many archive
- sites also identity themselves as such in their USENET Mapping Project map
- entry. Some sites have even been listed in this column. These sites allow
- remote computers to access their archives to retrieve the sources. How you
- access the archives depends on where they are, and how that site has set up
- access. Most archives are available for either FTP (file transfer protocol, a
- TCP/IP protocol used by internet sites) or UUCP access and a few even allow
- both.
- If a site supports FTP access, you need to be on the Internet to access them.
- FTP allows for opening up a direct connection to the FTP server on their
- system and transferring the files directly to your system. FTP will prompt for
- a user name and optionally a password. Most FTP archive sites allow the user
- name anonymous. FTP may then prompt for a password; any password will work,
- but convention and courtesy dictate that you use your name and site address
- for the password.
- Recently CompuServe and AOL have announced plans to provide FTP support,
- however there are already many local Internet access providers across the
- country. I've seen a rash of books lately that list various Internet
- providers. A trip to the local computer bookstore should provide you with a
- list from which to start your search.
- If a site supports UUCP access, anyone with UUCP can access the archives. Most
- sites of this type publish a sample entry for the Systems (L. sys) file
- showing the system name, phone number of their modems, the connection speeds
- supported, and the login sequence. Using the uucp command you can poll the
- system directly and retrieve the software. Many sites post time-of-day
- restrictions on access to their modems. Courtesy dictates that you follow
- their requests, and some sites use programs to enforce the limits. Be sure to
- call early enough to complete your transfer in the allotted time.
-
-
- How to Decide What to Retrieve
-
-
- When I list a package archived in the moderated groups I provide five pieces
- of information for each package: the Volume, Issue(s), archive name, the
- contributor's name, and their electronic mail address. My listing will
- explicitly name the volume and issue numbers. I show the archive name in
- italics, and list the contributor's name followed by their electronic mail
- address, in angle brackets (<>).
- To locate a package via WAIS or archie, use the archive name -- the short,
- one-word name in italics shown with each listing. To find the file at an
- archive site, use the group name (listed in this column's section header), the
- volume name, and the archive name. Most archive sites store the postings as
- group/volume/archivename. The issue numbers tell you how many parts a package
- was split into when posted. You can check the issue numbers to be sure you get
- all of the parts.
- I also report on patches to prior postings. These patch listings also include
- the volume, issue(s), archive name, the contributor's name, and their
- electronic mail address. Different archive sites store patches differently.
- Some sites store patches along with the original volume/archive name of the
- master posting. Some sites store them by the volume/archive name of the patch
- itself. The archive name listed is always the same for both the patch and the
- original posting.
- alt. sources, being unmoderated, does not have volume and issue numbers. So I
- report on the date in the "Date:" header of the posting and the number of
- parts in which it appeared. If the posting was assigned an archive name by the
- contributor, I also report on that archive name. Archive sites for alt.sources
- are harder to find, but they usually store things by the archive name.
-
-
- Where to Retrieve It
-
-
- Let's say you've read this column, and you've figured out what you want, and
- how to specify it in terms of archive name, group, volume name, etc. The
- problem then is finding out which sites archive which groups, and how to
- access these archives. I again refer to the articles by Kent Landfield of
- Sterling Software and Jonathan I. Kames of the Massachusetts Institute of
- Technology, posted to comp.sources.wanted and news.answers. These articles
- appear weekly and explain how to find sources.
- As a quick review, here are the steps:
- I. If you know which sites archive the group, derive the path name for the
- item. Most archive sites keep their information in a hierarchy ordered first
- on the group, then on the volume, and last on the archive name. These together
- usually make up a directory path, as in comp.sources.unix/volume22/elm2.3. In
- that directory you will find all of the articles that made up the 2.3 release
- of the Elm Mail User Agent that was posted in Volume 22 of the
- comp.sources.unix newsgroup. If you do not know the archive name, but do know
- the volume, each volume also contains an index file that can be retrieved and
- read to determining the archive name. One common publicly accessible archive
- site for each of the moderated groups in this column is UUNET (ftp.uu.net).
- II. If you do not know which sites archive the groups, or whether any site is
- archiving a particular item, (because they are not archiving the entire
- group), consult archie. (See "Archie At Your Service," CUJ, August 1991, Vol.
- 9, No. 8). archie is a mail response program that tries to keep track of sites
- reachable via FTP that have sources available for distribution. Even if you
- cannot access the archive site directly via FTP, it is worth knowing that the
- archive site exists because there are other ways of retrieving sources
- available via FTP.
- III. If you know the name of the program, but do no know what group it was
- posted in, try using archie and search based on the name. Since most sites
- store the archives by group and volume, the information returned will tell you
- what newsgroup and volume it was posted in. Then you can retrieve the item
- from any archive site for that newsgroup.
- IV. If you do not even know the name, but know you are looking for source code
- that does xxx, retrieve the indexes for each of the newsgroups and see if any
- of the entries (usually listed as the archive name and a short description of
- the function) look reasonable. If so, try those. Or, make a query to archie
- based on some keywords from the function of the software and perhaps it can
- find items that match.
-
-
- CD-ROM Archives Available
-
-
- Even if the above access methods don't work for you, you can buy CD-ROMs that
- archive sources from USENET and from other services as well. Two of the larger
- publishers are Walnut Creek CD-ROM and Prime Time Freeware.
- Walnut Creek CD-ROM, 1547 Palos Verdes Mall, Suite 260, Walnut Creek, CA (800)
- 786-9907 or (510) 947-5996, publishes several CD-ROMs each year. Some contain
- the Simte120 MS-DOS Archive, others the X and GNU archives, and still others
- MS-Windows sources, and other collections of sources and binaries. Disks run
- from $25 to $60 each (varies by title) plus shipping. In addition, they offer
- those hard-to-find CD-CADDYs at reasonable prices.
- Prime Time Freeware, 370 Altair Way, Suite 150, Sunnyvale, CA 94086, (408)
- 738-4832, <ptf@cfcl.com), publishes twice a year a collection of freely
- distributable source code, including the complete USENET archives. Their disks
- run about $60 each set plus shipping. They also offer a standing subscription
- plan at a discount.
- Now it's time to look at some of the new stuff on the 'net.
-
-
- Perl Shines Bright Anew
-
-
- Perl 5.000 was finally released after a long beta period. Perl 5.000 is a
- cleaner and more portable version that supports a greatly simplified grammar
- and a tighter, faster interpreter. Perl, the interpreter for the Perl
- Programming Language, is a combination of sed, the shell, and awk. Perl is an
- extremely useful tool for writing scripts and filters. It has been ported to
- UNIX and VMS, and ports of 5.000 to MS-DOS and Windows/NT are in the works.
- Additional features in 5.000 over version 4.036 include the following:
-
- optional compile-time restrictions on dubious constructs
- greatly improved diagnostics
- both lexical and dynamic scoping
- anonymous data types: scalars, lists, hash table, functions
- arbitrarily nested data structures
- modularity and reusability
- object-oriented programming
- package constructors and destructors
- embeddability and extensibility
- dynamic loading of C libraries
- a POSIX 1003.1 compliant interface
- multiple simultaneous DBM implementations: dbm, sdbm, ndbm, gdbm, and berkeley
- db
- operator overloading on arithmetic types -- supplied: Complex, BigInt, and
- Bigfloat
- regular-expression enhancements
- extensions: curses, X11 support via Sx (Athena, Xlib), and Tk.
- In addition the documentation has been entirely overhauled into both standard
- man interface and HTML versions.
- Perl 5.000 was posted as perl in Volume 45, Issues 64-128 of comp.sources.misc
- and contributed, as usual, by Larry Wall <lwall@netlabs.com>.
-
-
- Slip-Like Connections
-
-
- Michael O'Reilly wrote and Bill C. Riemers <bcr@physics.purdue.edu> submitted
- term-2.1.2 for Volume 28, Issues 164-172 of comp.sources.unix.term simulates
- many Slip-like features through an ordinary user's account. term requires no
- kernel support on either end, and no root access on either end. It is built to
- run over a modem to connect a non-internet machine with an internet machine.
- If your machine is of the former type, and you don't have slip/ppp, then term
- is for you. If you do have slip/ppp, then you should probably use it instead.
- term is run at both ends, and does multiplexing, error correction, and
- compression across the serial link between them. term is designed to be as
- efficient as possible, with good response time, even over slow modems. term
- behaves the same from both ends. A user on either machine can connect to the
- other.
- term includes several clients, including the following:
- trsh This client runs a shell on the remote end. It's like a normal login
- (similar to "rsh" without the need to specify a hostname).
- tupload The usage is tupload <local file> -as <remote file>.file uploads a
- file, taking the name of the local file and the optional argument as the name
- of the remote file. This format is a bit more robust than that of "rcp," but
- at the price of not allowing specification of a different host or username.
- txconn This client hangs around in the background waiting for X connections,
- and re-directs them to the local X server. txconn is intended to be run on the
- remote machine.
- tredir This client lets you alias a port on one system to another, i.e.,
- 'tredir 4000 23' run on host. 'a' means that anyone telneting to port 4000 on
- 'a' will get a login prompt of machine 'b'.
- tshutdown Terminates term.
-
-
- Magic Image Manipulation
-
-
- ImageMagick, the rather complete image manipulation package for X, was updated
- by Cristy <cristy@eplrx7.es.duPont.com> for Volume 22, Issues 35-87 of
- comp.sources.x.
- ImageMagick is an X11 package for display and interactive manipulation of
- images. The package includes tools for image conversion, annotation,
- compositing, animation, and creating montages. ImageMagick can read and write
- many of the more popular image formats (e.g. JPEG, TIFF, PNM).
- ImageMagick includes the following:
- display This is a machine-architecture-independent image and display program.
- It can display an image on any workstation display running an X server.
- display can read and write many of the more popular image formats (JPEG, TIFF,
- PNM, etc.). You can perform these functions on the image:
- display information about the image
- write the image to a file
- print the image to a PostScript printer
- delete the image file plus load an image from a file
- display the former image
- select the image to display by its thumbnail rather than name
- undo last image transformation
- restore the image to its original size
- halve the image size
- double the image size
- resize the image
- trim the image edges
- clip the image
- cut the image
- flop image in the horizontal direction
- flip image in the vertical direction
- rotate the image 90 degrees clockwise
- rotate the image 90 degrees counter-clockwise
- rotate the image
- shear the image
-
- invert the colors of the image
- Perform Histogram Equalization on the image
- Perform histogram normalization on the image
- gamma-correct the image
- reduce the speckles within an image
- detect edges within the image
- convert the image to grayscale
- annotate the image with text
- set the maximum number of unique colors in the image
- add a border to the image
- composite image with another
- edit an image pixel color
- edit the image matte information
- add an image comment
- display information about this program
- discard all images and exit program
- change the level of magnification
- import This program reads an image from any visible window on an X server and
- outputs it as an image file. You can capture a single window, the entire
- screen, or any rectangular portion of the screen. You can use the display (see
- display(1)) utility for redisplay, printing, editing, formatting, archiving,
- image processing, etc. of the captured image.
- You can specify the target window by ID or name, or select by clicking the
- mouse in the desired window. If you press a button and then drag, a rectangle
- will form which expands and contracts as the mouse moves. To save the portion
- of the screen defined by the rectangle, just release the button. The keyboard
- bell rings once at the beginning of the screen capture and twice when it
- completes.
- animate This program displays a sequence of images on any workstation display
- running an X server. animate first determines the hardware capabilities of the
- workstation. If the number of unique colors in an image is less than or equal
- to the number the workstation can support, animate displays the image in an X
- window. Otherwise, animate reduces the number of colors in the image to match
- the color resolution of the workstation before display.
- animate's color reduction capability allows a continuous-tone 24 bits/pixel
- image to be displayed on a 8-bit pseudo-color or monochrome device. In most
- instances, the reduced color image closely resembles the original.
- Alternatively, a monochrome or pseudo-color image sequence can display on a
- continuous-tone 24 bits/pixels device.
- montage This program creates a composite image by combining several separate
- images. The images appear tiled on the composite image with the name of each
- image optionally appearing just below the individual tile.
- mogrify This program applies transforms to an image or a sequence of images.
- Possible transforms include image scaling, image rotation, color reduction,
- and others. The transmogrified image overwrites the original image.
- convert This program converts an input file using one image format to an
- output file with a differing image format. By default, convert determines the
- image format by its magic number. To specify a particular image format,
- precede the filename with an image format name and a colon (e.g. ps:image) or
- specify the image type as the filename suffix (e.g. image.ps). Specify file as
- - for standard input or output. If file has the extension .Z, the file is
- decoded with uncompress.
- combine This program combines images to create new images.
- segment This program segments an image by analyzing the histograms of the
- color components and identifying units that are homogeneous with the fuzzy
- c-means technique. The scale-space filter analyzes the histograms of the three
- color components of the image and identifies a set of classes. segment uses
- the extents of each class to coarsely segment the image with thresholding. It
- determines the color associated with each class by the mean color of all
- pixels within the extents of a particular class. Finally, segment assigns any
- unclassified pixels to the closest class with the fuzzy c-means technique.
- xtp This is a utility for retrieving, listing, or printing files from a remote
- network site, or sending files to a remote network site. xtp performs most of
- the same functions as the ftp program, but does not require any interactive
- commands. You simply specify the file transfer task on the command line and
- xtp performs the task automatically.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Editor's Forum
- I have to say that I feel a little sorry for Intel. It takes a lot of
- microcode to do what a Pentium does. To expect every byte of that code to be
- correct is asking a lot. Those of us who work on intimate terms with
- microprocessors -- particularly the first batch or two of a new breed -- know
- that microcode bugs are actually rather common. I can't count the number of
- kludges I've added to code generators, and to runtime support functions, to
- sidestep bad instructions in one or more members of a family of CPUs.
- The effects of buggy microcode are usually not quite so pernicious as the one
- that haunts a couple million Pentia. If a program hangs, or goes barmy, you
- tend not to respect the numbers it has just generated. You are also motivated
- to change the program until it avoids acting demonstrably nutty.
- But when the numbers look fine, or maybe just a little suspicious, you're more
- inclined to doubt your intuition. After all, the whole idea of computers is to
- be very fast and very accurate in doing rather stupid things. We neither
- expect nor want creativity in floating-point arithmetic.
- I've been writing and testing math functions for several decades now. While I
- keep getting better at it, I still confess to shipping more than a few buggy
- lines of code. It's horribly easy to lose bits of precision, particularly for
- extreme argument values, and not catch the problem even with a fairly
- responsible test discipline. When you're piecing together a multipart result
- -- as the Pentium evidently does on a floating-point divide -- it's easy to
- lose a bit or two out of the middle as well.
- Where I fault Intel is in their public relations. You don't hide such a
- serious flaw from your customers. When it becomes widely known, you don't pooh
- pooh legitimate expressions of concern. And when you fix the product, you
- don't make customers jump through hoops to prove they deserve a properly
- functioning replacement. At least Intel eventually had the sense to wise up
- and apologize.
- I have observed more than once that software is seen more as a subscription
- service than a one-time sale. I have also predicted that the increasing use of
- microcode, ROMs, and EPROMs will shift the perception of more and more
- hardware in the same direction. Intel, to their credit, has created a mass
- market that expects regular and dramatic improvements in processor technology.
- It's too bad for them that they were slow to recognize the change in public
- perception that comes with this success, and to act accordingly.
- Those of us who deliver products based on C and C++ should view this episode
- as a cautionary tale. We need to check our work as thoroughly as budgets and
- schedules allow. Then we need to stand ready to fix the bugs that inevitably
- go out the door. For many of us, writing software is a happy end in itself,
- but to our customers the software is just a means to a different end.
- P.J. Plauger
- pjp@plauger.com
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- New Products
-
-
- Industry-Related News & Announcements
-
-
-
-
- Rogue Wave Introduces DBtools.h++
-
-
- Rogue Wave Software has introduced DBtools.h++, a library of foundation
- classes to support C++ programmers in creating SQL database applications.
- DBtools.h++ provides C++ components for object-oriented development on the
- client side, while retaining the benefits of their installed RDBMS.
- DBtools.h++ provides developers with a portable, cross-platform interface to
- SQL databases. This interface bridges the relational and object-oriented
- models by encapsulating fundamental relational constructs such as tables,
- rows, and cursors in C++ classes. Other features of DBtools.h++ include
- facilities for manipulating dates, times, strings, and in-memory data
- structures.
- DBtools.h++ consists of 36 public classes for encapsulating relation
- constructs and data types, plus 100 data structures and utility classes found
- in Tools.h++, which is integrated into DBtools.h++. DBtools.h++ also binds the
- basic data types available in relational database systems to its own
- higher-level C++ data structures. DBtools.h++ achieves portability across
- databases through a two-tiered architecture that includes a public interface
- and separate Access Libraries for the various supported databases. Because
- DBtools.h++ supports shared libraries, UNIX developers can switch between
- databases without relinking, just as Windows developers do with DLLs.
- DBtools.h++ supports ORACLE7, Sybase SQL Server, Microsoft SQL, and ODBC
- (Windows only). Prices for DBtools.h++ start at $395 for Windows and $495 for
- UNIX. For more information contact Rogue Wave Software, Inc., 260 S.W.
- Madison, P.O. Box 2328, Corvallis, OR 97339; (800) 487-3217 or (503) 754-3010;
- FAX: (503) 757-6650.
-
-
- ObjectSpace Announces Product Line
-
-
- ObjectSpace, Inc. has announced an object-oriented product line that supports
- C++ and Smalltalk. The products are ObjectSystems, ObjectSockets,
- ObjectMetrics, and ObjectCatalog. The products give developers a variety of
- solutions depending on their need: ObjectSystems is for C++ developers using
- UNIX, ObjectSockets is for Smalltalk developers using TCP/IP communications,
- ObjectMetrics gathers object-oriented metrics for Smalltalk developers, and
- ObjectCatalog is a corporate reuse facility.
- ObjectSystems is a C++ framework for cross-platform UNIX systems and
- development. Developers can either use the entire ObjectSystem library as a
- framework for UNIX development or in conjunction with Rogue Wave Tools.h++.
- Features of Objectsystems include support of IPC mechanisms, including pipes,
- sockets, message queues, shared memory, and semaphores; sending objects over a
- UNIX IPX mechanism or storing them on disk; and an eventhandling subsystem
- that integrates with C++ exception handling.
- ObjectSockets is a class library with 40 classes representing the aspects of
- TCP/IP communications, including TCP sockets, UDP sockets, IP addresses,
- hosts, network protocols, and services. ObjectSockets comes with source code
- and a user guide. Operated via a graphical interface, Objectmetrics gathers
- metrics that can indicate problem areas such as overly complex code,
- unnecessary coupling between modules, and programmer productivity. Features of
- the reuse facility, ObjectCatalog, include automatic publication of C++ and
- Smalltalk classes; publish and search capabilities for descriptions of
- classes, frameworks, and documents; user-defined classifications; searching of
- local and distributed catalogs; and the capability to register interest in a
- component.
- ObjectSystem is priced at $875 per user. ObjectSockets is priced at $695 per
- user, and ObjectMetrics is priced at $595 per user. Site licensing is
- available. For more information contact ObjectSpace, Inc., 14881 Quorum Dr.,
- Suite 400, Dallas, TX 75240; (214) 934-2496; FAX: (214) 663-3959.
-
-
- MainSoft Upgrades MainWin
-
-
- MainSoft Corporation has upgraded MainWin, its "Windows API to UNIX" cross
- development technology. The MainWin technology is an implementation of the
- Windows API directly on UNIX. The MainWin library, the equivalent of the
- Windows DDL on a PC, executes in a high-performance, native RISC code. A
- ported application's C/C++ code is also compiled into native RISC code. Users
- of MainWin-ported applications can specify whether the applications have the
- look of Windows or Motif.
- Mainsoft has access to the Windows source code through a licensing agreement
- with Microsoft, facilitating the development of MainWin. Features of MainWin
- 1.1 include a Dynamic Data Exchange Management Library (DDEML), and support
- for 16-bit Windows 3.1 and 32-bit Windows NT code. Another feature of MainWin
- 1.1 is that both 16- and 32-bit support is provided by a single library.
- MainWin 1.1, a shared library on UNIX workstations, can support applications
- compiled from Windows 3.1 and Windows NT source code executing simultaneously.
- In addition, MainWin 1.1 adds support for many of Win32-specific APIs. MainWin
- 1.1 also includes increased window and repaint speeds. According to MainSoft,
- display speeds for complex dialog boxes have more than doubled.
- MainWin 1.1 supports Sun SPARC with Solaris 2.2, Sun SPARC with SunOS 4.1, HP
- 9000/7000 and 8000 with HP-UX 9.0, IBM RS/6000 with AIX 3.2, and SGI Iris or
- Indigo with IRIX 5.1. For more information contact MainSoft Corporation, (408)
- 774-3405.
-
-
- ACE Releases ACE EXPERT
-
-
- ACE Associated Computer Experts has released the ACE EXPERT SPARC Compilers.
- The ACE EXPERT SPARC Compilers combine EXPERT front ends for ANSI C, K&R C,
- FORTRAN 77, Pascal, and Modula 2 with a computer generated SPARC code
- generator, which has been generated from a concise architecture description.
- According to the company, the ACE EXPERT SPARC Compilers have undergone
- extensive field testing, and the robustness resulting from the use of
- generators was proven using large industrial programs. A feature of the ACE
- EXPERT SPARC Compilers is that program modules written in different languages
- can be mixed by linking them together.
- The ACE EXPERT SPARC Compilers are available on CD-ROM for Solaris 2. The ACE
- EXPERT SPARC Compilers interface to the Solaris linkage editor and to
- debuggers such as TotalView and dbx. The CD-ROM containing the five ACE EXPERT
- SPARC Compilers and online documentation is priced at $499. For more
- information contact ACE Associated Computer Experts, Inc., Van Eeghenstraat
- 100, 1071 GL Amsterdam, The Netherlands; +31 20 6646416; FAX: +31 20 6750389;
- Telex: 11702 (ace pl).
-
-
- StratosWare Ships MemCheck v3.0 for Windows
-
-
- StratosWare has begun shipping MemCheck v3.0 for Windows, an automatic
- error-detection tool for C/C++ programmers. With the inclusion of a single
- header file, MemCheck for Windows tracks an application's GDI objects
- including device contexts, pens, bitmaps, brushes, fonts, metafiles, and
- regions. GDI objects that are not released are identified by file and line.
- MemCheck will notify users of invalid operations as well as of the use of
- invalid Windows or object handles. MemCheck also tracks resources loaded or
- created by an application. In addition, MemCheck v3.0 for Windows detects
- memory overwrites and underwrites, memory leaks, invalid handle/pointer usage,
- out-of-memory conditions, and other memory errors in GlobalAlloc(),
- LocalAlloc(), malloc(), free(), and allocation/reallocation functions. Data
- transfer functions are also intercepted by MemCheck to check for overwrites to
- heap and other variables.
- MemCheck v3.0 integrates with existing C/C++ code and works at run time to
- identify errors by source file and line number in the source code. Developers
- can use MemCheck in combination with other debuggers such as CodeView or
- TurboDebugger. Developers may ship applications with MemCheck linked in,
- royalty-free, allowing detection of errors at customer or test sites.
- MemCheck v3.0 supports Microsoft C/C++ 7.0, Visual C/C++, and Borland C++
- 2.0-4.x. MemCheck v3.0 for Windows is priced at $139 and the upgrade price
- from v2.1 is $59. For more information contact StratosWare Corporation, 1756
- Plymouth Road, Suite 1500, Ann Arbor, MI 48105-1890; (800) 933-3284; BBS:
- (313) 996-2993.
-
-
- Reasoning Systems Releases Software Refinery 4.0
-
-
- Reasoning Systems has released Software Refinery 4.0, which builds software
- source code analysis and conversion tools on UNIX workstations. Tools that can
- be developed by Software Refinery include reverse-engineering tools that help
- users understand and modify unfamiliar legacy systems; conversion tools that
- port source code to other programming languages, operating systems, and
- databases; and quality assurance tools that point out bugs and unmaintainable
- code.
- Software Refinery includes a parser generator, which lets tools work with the
- source code. It also includes an object base, which stores a representation of
- source code and associated information between sessions and allows sharing of
- data on a LAN. Software Refinery's unit-level incremental compilation and
- dynamic linking provide a short edit-compile-debug loop. A GUI toolkit,
- integrated in Software Refinery, lets users develop graphical interfaces using
- windows, menus, and dialog boxes. Also included in Software Refinery is a
- high-level language, which lets users develop programs to analyze and modify
- source code using objects, logic programming, pattern-matching, and
- transformation rules.
- Features of Software Refinery 4.0 include Workbench, a library of reusable
- reverse engineering components including structure charts, flow graphs, and
- coding standards; a "surface syntax" facility which overlays the
- object-oriented tree representation of source code with the original program
- text; windows types (outline, tables, and directory browsers); and a graphical
- data inspector.
- Software Refinery 4.0 supports UNIX workstations on Sun, IBM, and HP
- platforms. For more information contact Reasoning Systems, 3260 Hillview Ave.,
- Palo Alto, CA, 94304; (415) 494-6201; FAX: (415) 494-8053.
-
-
-
- AJS Publishing Introduces VBXpress
-
-
- AJS Publishing, Inc. has introduced VBXpress Custom Control Design System.
- With VBXpress, a user can design original screen objects for Windows, such as
- custom sliders, tool bars, floating tool palettes, flyouts, rocker buttons,
- spin buttons, and graphic command buttons. VBXpress then turns the screen
- object control into a VBX custom control without programming. The resulting
- VBX controls can be used to add user-interface features to programs written in
- Visual C++, Visual Basic, Borland C++ 4.0, dBASE for Windows, or other
- VBX-conforming languages or application development systems.
- VBXpress creates VBX controls based on the definitions specified by the user
- through the Control Editor. The VBXpress Control Editor is a menu-driven,
- point-and-click program, which assembles image, color, text, behavior, and
- style selections into a screen object. Images can be applied from four
- available palettes or created new and imported from an external bitmap editor.
- The control can then be customized from a variety of available events and
- properties. When a new control's definition is finished, it is stored in a
- VBXpress resource file with a VBM extension. VBM files can be reopened later
- for modifications and additions. VBXpress will automatically generate the new
- VBX file containing the functionality of the newly-defined control. According
- to AJS, the resulting file is a fully-compatible, Level One VBX control.
- The VBXpress Custom Control Design System includes the Control Editor program,
- VBXpress Conductor wizard, documentation, and help system. The VBXpress Custom
- Control Design System is priced at $299. There are no royalties or runtime
- fees. For more information contact AJS Publishing, Inc., P.O. Box 83220, Los
- Angeles, CA 90083; (800) 992-3383 or (310) 215-9145; FAX: (310) 215-9135
-
-
- Software Blacksmiths Ships C-DOC 6.0
-
-
- Software Blacksmiths has begun shipping C-DOC 6.0, an upgrade of their
- automatic program documentation tool for Windows 3.1, Windows NT, OS/2, and
- DOS. C-DOC generates information that is used to modify and maintain C/C++
- software products. C-DOC includes six modules: C-CALL, C-CMT, C-LIST, C-REF,
- C-METRIC, and C-BROWSE.
- Features of C-DOC 6.0 include a native Windows NT/Win 32 and Windows/Win16
- version, support for long Win32 and OS/2 file names, integrated graphical tree
- and text viewing, and caller/called and called/caller function hierarchy
- trees. Other features of C-DOC 6.0 include a RUN option in help; RTF for input
- in word processors like Microsoft Word, Lotus AMI, or WordPerfect; and
- 1,000,000 line capacity for Win32, OS/2, and extended DOS.
- C-DOC 6.0 supports Windows 3.1, NT, OS/2, or DOS-compatible PCs. C-DOC 6.0
- standard edition for DOS, supporting up to 10,000 source lines in multiple
- files and directories, is priced at $199. C-DOC Professional, supporting the
- mentioned environments and programs of up to 1,000,000 lines in multiple files
- and directories, is priced at $299. Individual C-DOC modules are priced at $69
- each. For more information contact Software Blacksmiths, Inc., 6064 St. Ives
- Way, Mississauga, Ontario, Canada L5N 4M1; (905) 858-4466; FAX: (905)
- 858-4466; BBS: (905) 858-1916; E-mail: swbksmth@flexnet.com.
-
-
- Microware Announces FasTrak
-
-
- Microware Systems Corporation has announced FasTrak real-time,
- cross-development tools for Windows. Supporting Windows 3.1 on 386/486 and
- Pentium systems, FasTrak creates C applications for Intel 80x86, Motorola
- 680x0, and PowerPC systems that run Microware's OS-9 RealTime Operating
- System. Communications between the Windows host and OS-9 target is conducted
- via Ethernet TCP/IP or serial link.
- FasTrak integrates tools that automate the creation, debugging, analysis, and
- management of complex real-time software development projects. Bundled with
- Microware's Ultra C optimizing C compiler, FasTrak includes tools for code
- creation and revision, code generation, source-level debugging, system and
- application profiling, and software version control. FasTrak for Windows
- contains three core modules: a MakeFile Editor, which contains a pushbutton
- utility for generating makefiles; the FastFix Debugger, a C source level
- debugger, which resides on the host; and the FasTrak Target Monitor, which
- adds system-level debug and application profiling capabilities.
- FasTrak for Windows is priced at $3,750. For more information contact
- Microware Systems Corporation, 1900 N.W. 114th St., Des Moines, IA 50325-7077;
- (800) 9000 or (515) 224-1929; FAX: (515) 224-1352; Internet:
- info@microware.com.
-
-
- SciTech Publishes Software For Science Volume 26
-
-
- SciTech International, Inc., has published Volume 26 of Software For Science,
- a comprehensive catalog of scientific and technical software for DOS, Windows,
- Macintosh, and UNIX workstations. Volume 26 lists 1,750 products, including
- 250 new products, as well as articles that contain information about
- scientific and technical problems, tips, and "how to" information on buying
- complex scientific and technical software. SciTech's product database contains
- information on 3,000 scientific and technical tools.
- Software for Science Volume 26 is free of charge. Electronic versions of
- Software for Science are available for Windows and the Internet. For more
- information contact SciTech International, Inc., Bailiwick Court Bldg., 2231
- N. Clybourn Ave., Chicago, IL 60614-3011; (800) 622-3345 or (312) 472-0444;
- FAX: (312) 472-0472; E-mail: info@scitechint.com.
-
-
- SSI Upgrades Link&Locate 386 and Announces Tools for Embedded Processors
-
-
- Systems & Software, Inc. (SSI) has upgraded Link&Locate 386, their
- Linker/Locator/Builder for embedded x86 software development using C/C++.
- Features of Link&Locate 386 v2.0 include 32-bit COFF support, run-time
- support, DOS and Windows NT executables, and control of segment ordering.
- Link&Locate 386 v2.0 supports the following compilers and assemblers:
- Microsoft Visual C++ 1.0 for NT, MetaWare High C/C++ for NT, WATCOM C/C++/32
- 10.0, Microsoft MASM 6.11, and Phar Lap 386ASM 6.x. Native versions of
- Link&Locate are offered for DOS and NT. The Link&Locate 386 package includes
- EPROM programmer utilities, a librarian, and is interoperable with SSI's
- family of 32-bit development tools and debuggers.
- In another announcement, SSI has begun offering a series of tools to support
- application development based on AMD's AM486SE, AM386EM, and AM186EM embedded
- processors. SSI offers three lines of development tools that are compatible
- with the AM386 family: SP/Tools, CV/Tools, and OMF/Tools. SP/Tools is a family
- of tools designed to extend MetaWare's High C/C++ 32-bit compiler and
- Microsoft's MASM assembler for embedded development with the AM386/486 family.
- CV/Tools would be used in conjunction with Microsoft's Professional C/C++ and
- MASM for 16-bit real-mode embedded applications. OMF/Tools support the
- OMF86/286/386 file formats for development of real- or protected-mode embedded
- C applications.
- For more information contact Systems & Software, Inc., 18012 Cowan, Suite 100,
- Irvine, CA 92714-6809; (714) 833-1700; FAX: (714) 833-1900.
-
-
- Cavendish Releases NewTrack V3
-
-
- Cavendish Software Ltd. has released NewTrack v3 memory allocation tracker for
- C/C++ under Windows. NewTrack v3 supports Win 16 and Win32 development, using
- Borland C++ and Microsoft Visual C++.
- NewTrack v3 is offered as shareware. NewTrack v3 can be registered for $99 per
- user. NewTrack v3 site licenses start at $1000. NewTrack v3 can be downloaded
- from CompuServe. The Microsoft Visual C++ version can be found in the MSLANG
- forum, and the Borland C++ version in the BCPPWIN. Use the keyword NewTrack to
- locate the files. For more information contact Cavendish Software Ltd., 50
- Avenue Rd., Trowbridge, Wilts BA14 OAQ, England; (44) 1225-763598; FAX: (44)
- 1225-777359; Email: market@cavesoft.demon.co.uk.
-
-
- AIB Announces Sentinel II
-
-
- AIB Software Corporation has announced Sentinel II, a multi-platform, runtime
- memory testing tool for UNIX C and C++. Sentinel II, inserted into the build
- and test processes, detects memory access errors and memory leaks as they
- occur in the code. Sentinel II uses OMT technology, which lets Sentinel
- monitor the memory accesses performed by an application and detect memory
- errors. OMT is a process by which Sentinel converts object code into a
- system-independent representation of the program under test. Sentinel II then
- transfers the representation into machine-dependent object code with debugging
- capabilities.
- Sentinel II will support Sun SPARC, HP PA-RISC, and IBM RS6000 platforms. For
- more information contact AIB Software Corporation, 46030 Manekin Plaza,
- Dulles, VA 20166; (703) 430-9247; FAX: (703) 450-4560; Internet: info@aib.com.
-
-
- Black Ice Announces FAX C++ SDK
-
-
- Black Ice Software, Inc. has announced FAX C++ SDK. The FAX C++ Toolkit
- include standard device drivers for Windows to support Class 1, Class 2, and
- Class 2.0 fax modems. The FAX C++ is object oriented and provides both a C++
- interface and C User interface. FAX C++ handles a variety of image formats
- including TIFF/CCITT G3, PCX, DCX, and compressed and uncompressed Microsoft
- Device Independent Bitmaps (DIB). Other features of FAX C++ include support
- for both standard American and European paper sizes, send and receive queue
- management, dynamic configuration of faxing device, communication port
- control, and selection of page orientations. FAX C++ consists of a set of
- class definitions with 70 member functions which manage communicationn ports
- and fax modems. Additionally, FAX C++ supports data flow to and from classes
- such as TCommClassOne and TCommClassTwo.
- FAX C++ supports Microsoft Visual C++, Visual Basic, SQL Windows, Borland C++,
- and environments compatible with DLLs. FAX C++ is priced at $2,000 and is
- royalty free. For more information contact Black Ice Software, Inc., 113 Route
- 112, Amherst, NH 03031; (603) 673-1019; FAX: (603) 672-4112.
-
-
-
- Dart Communications Introduces PowerTCP
-
-
- Dart Communications has introduced PowerTCP, a set of protocol libraries that
- provide turn-key TCP/IP protocols for a flat license fee. PowerTCP is a second
- generation product based on the GCP++ line of tools, which provide 16-bit
- support for building TCP, UDP, TELNET, TFTP, and VT-220 applications over the
- Windows Sockets API. Features of the PowerTCP libraries include 32-bit
- support, FTP support, a C++ API, a new internal architecture, and expanded OEM
- licensing. PowerTCP provides both DLL (16- and 32-bit) and VBX interfaces.
- For more information contact Dart Communications, 6 Occum Ridge Rd.,
- Deansboro, NY 13328-1008; (315) 841-8106; FAX: (315) 841-8107.
-
-
- Menai Ships the Gamelon File I/O Library
-
-
- Menai Corporation has begun shipping Gamelon File I/O Library, an
- object-oriented programming tool for OS/2 and Windows/NT. Gamelon, written in
- C++ and built around the concept of object storage, lets developers build
- flexible file structures using combinations of aggregate and data objects.
- Features of Gamelon include aggregate object nesting, logical navigation, and
- automatic object tracking. The API provides free-form data storage and data
- association capabilities.
- In addition to the file I/O library and API for one programming language,
- Gamelon is packaged with several support tools, including the Browser, the
- Text Compiler, and the File Decompiler. Gamelon File I/O Library is priced at
- $395 for the Windows v3.x and $495 for the OS/2 and Windows NT versions. For
- more information contact Menai Corporation, 1010 El Camino Real, Suite 370,
- Menlo Park, CA 94025-4335; (800) 426-3566 or (415) 617-5730; FAX: (415)
- 853-6453; BBS: (415) 617-5726.
-
-
- Tower Technology Releases TowerEiffel/O2
-
-
- Tower Technology Corporation and O2 Technology, Inc. have released the
- TowerEiffel/O2 Object-Oriented Data Base Interface. Tower and O2 have
- integrated their products to allow TowerEiffel programs to use the features
- and capabilities of the O2 ODBMS, including persistence, collection, and
- referential integrity. The O2 object data model and Eiffel object notation
- include identity, class, types, encapsulations, multiple inheritance,
- redefinition, and late binding. The Tower Eiffel/O2 interface consists of a
- set of Eiffel classes and tools that allow persistent storage of Eiffel
- objects.
- The O2 ODBMS, including the TowerEiffel/O2 interface components, supports
- SunOS, Solaris, HPUX, and NEXTSTEP. O2 ODBMS is priced at $3,995 for a
- single-user license. For more information contact Tower Technology
- Corporation, Portland, OR; (512) 452-9455; E-mail: tower@twr.com.
-
-
- Advanced Computing Labs Introduces Neural++
-
-
- Advanced Computing Labs has introduced Neural++, a collection of neural
- networks packages, as a C++ library. Neural++ contains the code for the
- following neural networks: BP, CPN, Kohonen, and Outstar. Within a DOS/Windows
- C++ framework, Neural++ automates data scaling and Z-score data
- pre-processing. Features of Neural++ include C++ class programming references,
- math capabilities, and object persistence via iostreams.
- Neural++ also includes Math++, a set of numerical classes that provide access
- to matrices, vectors, linear algebra, random numbers, regression, simulation,
- and data analysis. Neural++ is priced at $269. For more information contact
- Advanced Computing Labs, P.O. Box 1547, West Chester, OH 45071; (513)
- 779-2716; FAX: (513) 779-4010.
-
-
- Sigma Software Ships OOPlot For C/C++
-
-
- Sigma Software has begun shipping OOPlot for C/C++, a C/C++ Windows version of
- OOPlot graphing software for business, science, and engineering. OOPlot for
- Windows includes manual and automatic scaling, labeling, overlapping windows,
- major and minor tick marks, colors, grids, and borders; linear, semi-log, and
- log-log axes; and line, scatter, bar, and 3-D pie graphs with real
- coordinates.
- OOPlot for Windows supports Borland C/C++ compilers and includes examples,
- sample programs, and interactive support. OOPlot for Windows is priced at $149
- and is royalty free. For more information contact Sigma Software, 15779
- Columbia Pike, Suite 360, Burtonsville, MD 20866; (301) 549-3320 FAX: (301)
- 549-3320.
-
-
- Blue Sky Upgrades RoboHELP
-
-
- Blue Sky's RoboHELP v3.0 is a Help Authoring tool for Windows and Windows NT,
- which supports both the U.S. and European versions of Microsoft Word 6.0.
- RoboHELP 3.0 is further integrated with Word 6.0, allowing users to use
- additional Word 6.0 features, such as smart quotes, emdashes, en-dashes,
- bulleted lists, numbered lists, and hanging indents.
- For more information contact Blue Sky Software Corporation, 7486 La Jolla
- Blvd., Suite 3, La Jolla, CA 92037; (800) 677-4946 or (619) 459-6365; FAX:
- (619) 459-6366; CompuServe: 71052,1641.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- We Have Mail
- Dear Mr. Plauger,
- I've been a C Programmer for 10 years now and have regrettably moved on to
- C++. The largest problem I've been having with C++ is the complexity of the
- language; the damn compiler does too much for you. In some cases it overrides
- what you've intended it to do. So I'm in full agreement that a new C proposal
- should keep with the long-standing spirit of C:
- the programmer must be trusted
- the compiler must not make decisions for the programmer
- the language must be small and simple
- With these concepts in mind, I propose the following additions to Mr. Jervis
- proposal:
- a) Constants expression are evaluated at compile time. This is the method used
- by C++ to evaluate constants and should be used by C. This feature is to get
- around this C problem:
- const int CHARBIT * 8;
- const int MAX_BUFSZ = 10 * CHARBIT;
- char *bufarr_ptr[MAX_BUFSZ];
- b) The article didn't mention inline functions. This feature should work for
- both function declarations and header declarations. Yes, we know it would make
- some work for the linker guys. But we could help by doing the following:
- #ifdef _NDEBUG
- # define INLINE inline
- #else
- # #define INLINE /* empty */
- #endif
- INLINE BOOL
- checkOverflow(const char * buf_ptr )
- {
- if (buf_ptr > bufarr_ptr) {
- return TRUE;
- }
- return FALSE;
- }
- We can debug this code by simply flipping compilation flags on/off.
- c) a thread-safe and reduced-error-checking standard C library. The same
- functionality would be used, but all the subroutines would be used as follows:
- typedef struct errorblk {
- char desc[96];
- int errno;
- char name[16]
- } errorblk_t;
- ...
- fh = ts_fopen(&errblk, filenm, "w+");
- The nice thing about using an errorblk_t is that you can call several C
- library calls without checking the return code:
- fh = ts_fopen( &errblk, filenm, "w+");
- ts_fwrite( &errorblk, fh, ....
- ts_lseek( &errorblk, fh, ...
- if ( errorblk.errno != TS_OKAY ) {
- ts_perror( &errorblk );
- }
- This also removes the use of errno and explicitly notifies the library creator
- that all ts_???? routines must be reentrant.
- d) Mr. Jervis did mention inheritance and virtual functions, which I feel are
- key features missing from C. Unfortunately, the mechanism to define the class
- access hierachy is missing from the proposal. The user cannot use the
- protected, public, and private keywords that C++ contains when declaring a
- derived class. An example would be:
- class base { .... }
- class derived /* protected */ base { ... }
- // class derived :: protected base { ... }
- I don't agree with Mr. Jervis's use of the derived keyword because it's
- already a standard, even though his logic is correct.
- e) The last thing I've alway wished C had was a method to reduce structure
- indirections. This is a feature that Pascal has with the with keyword.
- Frequently, I'm forced to create temporaries to track very long structure
- indirections. I've seen other programmers use #defines (really).
- Respectfully,
- David Dennerline
- d.dennerline@bull.com
- Dear Mr. Plauger,
- I have just read Bob Jervis' "All is Flux" article in the October 1994 issue
- of CUJ. I like the ideas he presents and am in total agreement with what he
- says. He has, however, left one issue out which I would like to comment on.
- That issue is how base class virtual methods are called from child classes.
- One of the powerful aspects of virtual methods is that they can be used to
- slightly modify the functionality of the base class. For example, a
- SpecialButton class may wish to distinguish itself from a normal Button class
- by adding an extra frame around the button drawn on the screen. To do that, it
- uses the capabilities of virtual methods to create its own Draw method. Rather
- than copy the screen drawing code in the Button class, it should be able to
- call the Button class's Draw method and then continue with framing the button
- so drawn.
- This is an important capability of virtual functions and I expect that it
- wasn't explicitly written in the article because it logically followed. But
- there are issues surrounding the syntax of how to call overridden methods. In
- C++ you can call the overridden method by prefixing it with the parent class
- name. In my example, the code would look something like this:
- class Button {
- public:
-
- virtual void Draw(void);
- ...
- };
- class SpecialButton : public Button {
- public:
- virtual void Draw(void);
- ...
- };
- SpecialButton::Draw() {
- Button::Draw();
- ... // draw frame around button
- }
- While in this example this makes sense, it can become confusing in larger
- hierarchies. For example:
- class Button {
- public:
- virtual void Draw(void);
- ...
- };
- class StateButton public Button {
- ... // doesn't override Draw
- };
- class SpecialButton : public StateButton {
- public:
- virtual void Draw(void);
- ...
- };
- SpecialButton::Draw() {
- StateButton::Draw();
- ... // draw frame around button
- }
- In this case, a second class is added, StateButton, which doesn't override the
- Draw method. In C++, the SpecialButton::Draw method can explicitly call the
- Button::Draw method, but this bypasses any virtual overrides which the
- StateButton class may have defined. Therefore, it is appropriate to call the
- StateButton::Draw method, even though it does not declare one! While the
- reason for using the StateButton::Draw syntax rather than the Button::Draw one
- can be explained, it is confusing for programmers just learning C++.
- Now, leaving C++ and its complexity behind, look at the equivalent C with
- Classes code for the first code snippet.
- class Button {
- public:
- virtual void Draw(void);
- ...
- };
- class SpecialButton inherit Button {
- public:
- virtual void Draw(void);
- ...
- };
- SpecialButton::Draw() {
- Button::Draw();
- ... // draw frame around button
- }
- This syntax still has the same level of confusion inherent in it as C++.
- To refresh our memories, I don't believe that C with Classes should not
- provide a similar capability. As I said, the ability to call overridden
- virtual methods provides most of the power of polymorphism.
- Instead of the class::method syntax, I would like to propose a syntax I first
- ran into in Object Pascal. (Don't throw it out just because it has Pascal
- roots.) The use of the keyword inherited allows for calling the immediately
- overridden virtual method. In this case, the code for the SpecialButton::Draw
- would look like:
- SpecialButton::Draw() {
- inherited Draw();
- ... // draw frame around button
- }
- Or some similar syntax. This makes it clear that the programmer knows that the
- override function is being called, ties the method of calling such a function
- in with the inherit keyword in the class declaration, and eliminates the
- confusion about which class name to prefix a overridden method with.
- There are some implications to what I just proposed. First, this syntax will
- only work because C with Classes implements single inheritance. Second, a
- virtual method can only call the full chain of overridden methods. (I am not
- sure if this is true, but in C++ it appears that you can bypass a level of
- virtual functions by using an older class in the explicit
- class-name::method-name syntax.)
- For what it's worth, I feel that this method of calling overridden virtual
- functions fits with C's feel better than the C++ syntax.
- Steven Kienle
- sckienle@pwinet.upj.com
- P.S. I feel compelled to remind all programmers that foobar is not the correct
- spelling of the word. Fubar is, as it is an acronym for "Fouled Up Beyond All
- Recognition." [Fouled is a euphemism -- pjp]
-
- Your concern is noted, along with many others. I repeat for emphasis that the
- goal of revising Standard C is to incorporate existing practice where it makes
- sense to do so, not to invent. -- pjp
- Dear Mr. Plauger,
- I completely agree with Bob Jervis's proposal on the evolution of C, and with
- your comment.
- I have been using C before, and I am using C++ now. Well, I must admit that I
- am actually only using a subset of C++, and should prefer to work with a less
- powerful but simpler, more reliable, and more predictable compiler.
- Now, having read Jervis's article, I believe that the proposed updated C is
- the language I need, and hope that we shall be able to use such a language as
- soon as possible.
- Please, keep us informed about it. Thank you.
- Sincerely,
- Emilio Morello
- Via Cantore 79
- 10095 Grugliasco (To)
- Italy
- Dear Sirs;
- Re: C Standard Effort "All is Flux"
- There are many "features" within the C language that need improving. I believe
- that C was in danger of death when the C committee met to establish the
- standard for C in 1985. It was nearly impossible to successfully write a
- production program of one million lines in C. The lead time between versions
- grew toward two years typically. Projects got dropped. Doom and gloom were the
- norms. Weird coding conventions like the "Hungarian" got invented and adopted.
- Bugs proliferated and "Lints" multiplied. Then the committee adopted function
- prototypes and type checking. The committee adopted additional stern measures
- and C survived.
- Now we should complete the typing of the C language. Make the old K&R style
- functions illegal. Remove the int default type specifier. Make the language
- fully typed. Make it possible for the compiler to kill the bugs. Too many
- programmers are ignoring the warnings generated by the compiler. It drives me
- to tears to read some code.
- Avast! I still see many compilers making new functions from improperly
- constructed casts. Casts! Aghast! Casts are currently the most abused and
- error causing feature of C. The C++ committee has seen the same defect. I say
- adopt the identical solution. Make casts explicit so that one can read them
- correctly six months after writing them. Make them so that the compiler can
- identify errors of syntax, instead of labeling them as casts.
- C has drawn to it programmers from other languages. I see published code that
- looks like COBOL! I won't mention the name of an MS Windows guru whose code
- examples begin with a long list of global function prototypes and defines. Not
- to mention that this style means flipping pages of code to find the
- prototypes. (Yea, some of us have to read this code stuff.) Localize these
- functions to some blocks of code. Put them into libraries or headers and let
- us use the "namespaces" feature of C++, or come up with a better solution!
- Then, also, the compiler sometimes calls the wrong function. It is the name
- scope role biting again. Or should we say, the lack of enough rules.
- Namespaces is the feature that I am calling for in this complaint. We can make
- purchased libraries easier to use. With this scope addition, we need type safe
- linking. Since many linkers fail to complain of external functions defined
- differently in two or more translation units, the whole portable C development
- environment needs deliberation. It would help to separate linkage and memory
- allocation identifiers so that keywords like static do not have two different
- meanings. At least, we can adopt the C++ const function ideal to replace the
- preprocessor #define. It surely would make for easier reading of source code.
- With this const comes its anti, the mutable. I know that the powers that be
- require the C++ committee to give to the C committee a listing of
- incompatibilities. Many above ideals can reduce this list without breaking
- existing code. (And what it does break, that code needs fixing even now.) The
- C committee can see if adopting others won't break existing code.
- These above ideals all reduce coding errors (unplanned "features"). Compiler
- writers can carry out the above in C without making C be like C++. Speaking of
- C++, we don't need another C++ language. If we add classes to C, we change it
- into a caricature of C++. As most C++ compilers come with either a C compiler
- included or a compatibility mode built-in, augmenting C with C++ object
- supporting features wastes our money. Indubitably, IBM and Microsoft and
- others have already invented pseudo-object mechanisms using C with their SOM
- and COM and CORBA technology. The act of adding classes to C would not make C
- classes usable with this existing technology. C with classes is C++. This
- kindly ideal only duplicates the wheel.
- Sincerely,
- Willlam L. Hartzell
- Garland, Texas 75044
- Post Script:
- I received the November 1994 issue just as I was about to mail this. Therein,
- I found that the C committee is going to do about what I am asking. However, I
- decided to send this on as my reinforcement to their efforts. Tallyho! Please
- forward.
- We won't have to worry about a shortage of opinions on how to revise Standard
- C. -- pjp
- Dear Mr. Plauger:
- I've enjoyed this magazine (CUJ -- how do we acronym it now? CUJ++, perhaps?),
- and your writing in general, for many years. Thanks for much useful
- information on many aspects of computer science, especially my favorite
- subjects, C and C++. BTW, I also like your science fiction!
- I recently wrote an improved (IMHO) replacement for the Windows API function
- TabbedTextOut, which required me to tokenize the output string so that each
- token could be drawn at the appropriate tab stop. Naturally, I turned to the C
- run-time function strtok to perform the tokenization, and discovered something
- which I found surprising:
- The function (I tried a few different versions) won't return a zero-length
- string when it finds two delimiters together. Example:
- "token 1\ttoken 2\t\ttoken 4"
- I anticipated (and required) that strtok would return these four strings:
- "token 1"
- "token 2"
- ""
- "token 4"
- However, (no surprise to you, I'm sure, since the version you published does
- this also), it only returned the three strings of non-zero length. Since the
- strings my program displays are user-defined and based on data coming from a
- database populated by a variety of programs, the possibility exists (actually,
- the certainty exists) that any (or several) field(s) could be zero-length.
- So, I had to write my own version which returned the four strings I needed.
- (No big deal, but I wish I'd found your book earlier, as it appears that
- simply commenting out a single line of your version of strtok would provide
- the behavior I expected).
- Also, I haven't found any description of strtok which describes the behavior
- at this level of granularity. My questions:
- 1. Do all strtoks behave this way, and have they always? I seem to remember
- doing just the opposite years ago; writing a strtok which did skip the
- zero-length strings because the library version didn't. If this memory is
- accurate, it was probably the Aztec compiler for the 6502, my first C compiler
- (sigh).
- 2. In discussion with a colleague, we wondered if this behavior was provided
- (or altered) to help handle free-form language parsing?
- 3. Why doesn't strtok provide this seemingly useful behavior as an
- alternative? Or another run-time function which provides the zero-length
- strings?
- Or, am I just confused? :-)
- Later,
- Kit Kauffmann
- kitk@mudshark.sunquest.com
- 73363,447 (Compuserve)
- We didn't explicitly change the behavior of strtok when we standardized it. On
- the other hand, there may have been some variation among implementations in
- the field. One of the desirable effects of standardization is to call
- attention to such variations and encourage vendors to eliminate them. (And, by
- the way, we've decided to keep the acronym for the magazine as CUJ.) -- pjp
- PJP,
- I have a letter here that is of a different concern. I need your help and the
- readers help. I am a young programmer, just started programming in C, and am
- interested in creating arcade style games. However the examples that came with
- my Borland Turbo C compiler (BGIDEMO.C) are very low-level graphics. (I find I
- can do the same in Quick Basic.) I really have a strong desire to learn
- high-level graphics (CD-ROM quality) programming, yet I cannot find any books
- on the subject. If there are any high-level graphics programmers out there
- please send me some kind of info on the subject. I would really appreciate
- some source code in C so I can learn (on disk or documented) how to do this.
- Whoever gives me the key will have my eternal programming gratitude.
- Thanks,
- Dan Strohschein
- 5822 Cassandra Dr.
- San Bernardino, CA 92407
- P.S. I mean high-level graphics and palette like the cover of C Users Journal,
- May 1994.
- This is not my forté. Anybody out there in the market for some eternal
- gratitude? -- pjp
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- "Olympic" Filtering for Noisy Data
-
-
- Bob Stout
-
-
- Bob Stout is Director of Software development for Bridgeway Software in
- Bellaire, Texas. Familiar as an ex-moderator of the FidoNet C language
- EchoMail conference and archivist for the "SNIPPETS" collection of free
- Csource code, Bob is also the author of 28 DOS shareware utilities plus
- libraries for the Symantec, Watcom, Microsoft, and Borland C and C++
- compilers, published by his own company, MicroFirm. Contact him at P.O. Box
- 428, Alief, TX 77411; bobstout@neosoft.com; or Fidonet 1:06/2000.6.
-
-
-
-
- The Real World
-
-
- Compared to the lowly office computer, the typical embedded system lives in a
- harsh environment. Embedded systems must often interact with events and
- processes occuring beyond the nearest desktop, or outside the safety of the
- computer room. The real world works on non-digital principles, so embedded
- systems typically perform some sort of analog data acquisition, and that
- analog data is subject to noise. For embedded system to perform appropriately,
- noise in analog input data streams must therefore be filtered.
- In this article I present a simple filtering technique that will work on a
- small CPU, and that is especially effective with impulse noise -- one of the
- most common and most pernicious types. This technique is not a cure-all, but
- with it you may avoid much more expensive solutions to your noise problems
- that would amount to overkill.
-
-
- Need a Short Cut?
-
-
- We already have lots of fixes for noise, both in hardware and in software. The
- list of available hardware includes traditional analog filters, EMI filters,
- and Digital Signal Processing (DSP) chips. These are all great solutions if
- they are already installed on your embedded system. If you have to add these
- components, they stop looking so attractive.
- Fancy algorithms seem appealing, at least on the surface. Over the years, many
- standard types of signal filters have been synthesized in software. These
- software filters will kill the noise, but they require a powerful CPU. Herein
- lies a problem. Many embedded systems still rely on 16- and even eight-bit
- CPUs because of their low cost and low-power operation. Any but the most
- trivial digital filtering techniques can easily use up much of the available
- bandwidth of these smaller CPUs, leaving little time left over for doing the
- work for which they were intended.
- The same hardware limitation applies to Finite Impulse Response (FIR),
- Infinite Impulse Response (IIR), correlation, and frequency-domain analysis.
- Computing correlations and frequency-domain analyses in real time actually
- requires more CPU horsepower than most of us have in our desktop systems! The
- above discussion raises the question: is there a short cut? Can a non-hardware
- filter remove noise without choking an embedded system CPU? The answer is yes,
- in some cases.
-
-
- Enter "Olympic" Filtering
-
-
- The noise in analog data will typically be one of two types, AC or impulse.
- You can hear both of these types of noise in your own home audio system. An
- example of AC noise is the line frequency hum heard when some component is
- poorly grounded or shielded. An example of impulse noise is when someone turns
- on a high-current appliance and you hear a pop or snap though the speakers.
- Averaging can work well with AC noise. By careful selection of the sampling
- frequency and filter interval, the effects of an interfering signal may be
- readily removed. However, impulse noise will still affect averaging in an
- unpredictable manner. Using a weighted average can improve the filter's
- responsiveness and rejection of AC noise, but again does little to help with
- impulse noise.
- What to do?
- Many years ago I borrowed an idea from the Olympics, where an athlete's scores
- were determined by throwing out the highest and lowest scores from the panel
- of judges and averaging the rest. I asked myself if I could eliminate my "bad"
- data in the same manner. Implemented in assembly language, the resulting
- algorithm was simple and proved to be remarkably effective. In the years
- since, I've used this same simple filtering technique successfully on dozens
- of embedded projects in several different languages. This technique has worked
- especially well in equipment mounted adjacent to, and powered from, the same
- power lines as 60+ HP electric motors that are switched on and off
- asynchronously at frequent intervals. It has also worked well in environments
- with analog inputs derived from potentiometer sensors, which get increasingly
- noisy over time.
- Why does olympic filtering work? All averaging filters depend on oversampling,
- i.e., you always have more data than you really need. When averaging, the
- redundancy in the "good" data makes a more significant contribution to the
- result than does the anomalous data. Any additive noise having a mean value of
- zero -- such as AC noise -- is a candidate for an averaging filter. Impulse
- noise presents special problems, however. Impulse noise is intermittent and,
- when it does occur, typically is not additive. Noise spikes tend to replace
- normal data rather than simply inducing an offset. Olympic filtering
- identifies probable anomalous data and removes it before the average is
- calculated. If there is no anomalous data, then the olympic filter responds
- the same as a simple averaging filter. Its only added cost is a requirement
- for oversampling, which provides two more samples per averaging period that
- will never actually be used.
-
-
- Implementation
-
-
- Implementing an olympic filter is simple. Store the acquired data in a simple
- circular buffer. To obtain an output value, sum the data, subtract out the
- high and low values, and divide the sum by the buffer size minus two. I've
- always used buffer sizes that were two plus an integral power of two, so that
- after removing the high and low values, I could calculate the average by
- shifting rather than division.
- In Listing 1 and Listing 2 I show a standard C implementation, since good
- quality C compilers are available for virtually any target platform you might
- choose. However, for many low-end CPUs, assembly language might be a better
- choice. I think that for embedded PCs, C++ would clearly be superior.
- (Circular buffer management in general, and filtering algorithms in
- particular, are especially well suited to a C++ implementation when writing
- for embedded platforms with available C++ compilers. Although omitted here for
- space, the following files are available on this month's code disk and online
- sources: CIRCBUF.HPP, a template-based circular-buffer class; OLYMFILT.HPP, an
- olympic filter derived from the circular-buffer base class; AVGFILT.HPP,
- another straight averaging filter for comparison, also derived from the
- circular-buffer base class; and CIRCBUF.CPP, a demonstration program.) The
- underlying algorithm retains its efficiency regardless of target environment
- or implementation tools.
- Listing 1 shows the header file CIRCBUF.H. Note that the buffer is embedded in
- a struct object containing a pointer to the actual buffer. This struct also
- contains the buffer's size, a pointer to the next location to fill, and a flag
- to indicate whether the buffer has been filled. This last variable is
- important. In any filtering algorithm, running the filter before the buffer
- has been filled will greatly skew the result. The filter output will only be
- valid to the extent that the buffer data are valid.
- CIRCBUF.C (Listing 2) defines the three functions essential to the filter's
- implementation: allocating a buffer of the required size, adding data to it,
- and running the olympic filter on it. CIRCBUF.C also includes a straight
- averaging filter for comparison and a sample main function which may be used
- for testing by simply defining the macro TEST.
- Both the olympic and averaging filters take a "snapshot," copying the buffer
- to temporary storage. In a typical system, the sample acquisition (and hence
- the add function) will run at intervals based on timer interrupts. Since these
- interrupts may occur asynchronously, good coding practice dictates that you
- somehow stabilize the data set before processing. Other alternatives would be
- to simply disable the timer interrupt while processing the buffer, or to
- perform data acquisition synchronously with other tasks. As written, the code
- presented really needs modification to disable the timer interrupts while
- taking the snapshot.
-
-
- Testing and Tuning
-
-
- Compiling the sample demo with the TEST macro defined easily demonstrates the
- differences between olympic and "straight" averaging filters. Using
- well-behaved data and a six sample buffer size results in comparable
- performance from the two filters. For example, entering
- CIRCBUF 1 2 3 4 5 6 7 8 9 10
- shows no difference between the results of the two types of filters, whereas
- entering
- CIRCBUF 3 4 4 5 -200 1000
- shows dramatically different results typical of using an olympic filter in the
- presence of significant impulse noise.
- Olympic filtering is clearly superior to an averaging filter on data
- containing significant impulse noise. Like other averaging filters, olympic
- filters can also be tuned for good performance in the presence of AC noise by
- setting a sample rate equal to 1/(noise_frequency * buffer_size). For example,
- if you are using an olympic buffer containing six samples, a sampling interval
- of 2.78 msec. will provide your system with good immunity from induced 60 Hz
- noise. Choice of buffer size also dramatically affects the outcome. Small
- buffers reject the most noise and respond to changing conditions more quickly,
- at the expense of potentially discarding the most good data. Large buffers
- take longer to fill and respond more slowly, but eliminate more AC noise.
-
-
-
- Give it a Try
-
-
- No one type of signal filtering works for all applications. I've briefly
- discussed some of the most common algorithms and their suitability to smaller
- embedded systems projects. The olympic filter is a simple algorithm with a
- successful track record operating in some of the noisiest environments
- imaginable. Although I've never read of this algorithm being published
- elsewhere, it's been in my bag of tricks for years. Try it on your next
- embedded project when noise is a problem. You may find, as I often have, that
- it's all you need.
-
- Listing 1 Header file for circular buffer C functions
- #ifndef _CIRCBUF_DEFINED_____LINEEND____
- #define _CIRCBUF_DEFINED_____LINEEND____
-
- typedef enum {CSIZ_TINY = 4, CSIZ_SMALL = 6, CSIZ_MEDIUM = 10,
- CSIZ_LARGE = 18, CSIZ_HUGE = 34} CSIZE;
-
- typedef enum {FALSE, TRUE} LOGICAL;
-
- typedef struct {
- CSIZE len;
- size_t next;
- LOGICAL full;
- short *buf;
- } CBUF;
-
- CBUF *cbuf_malloc(CSIZE);
- LOGICAL cbuf_add(CBUF *, short);
-
- short OlympicFilt(CBUF *);
- short AverageFilt(CBUF *);
-
- #ifndef MAX
- #define MAX(a,b) (((a) > (b)) ? (a) : (b))
- #endif
-
- #ifndef MIN
- #define MIN(a,b) (((a) < (b)) ? (a) : (b))
- #endif
-
- #endif // _CIRCBUF_DEFINED_____LINEEND____
-
- /* End of File */
-
-
- Listing 2 Circular buffer C functions
- #include <stdlib.h>
- #include <limits.h>
- #include "circbuf.h"
-
- /**********************************************************************/
- /* */
- /* cbuf_malloc() - Function to allocate a circular buffer on the heap.*/
- /* */
- /* Arguments: 1 - Size of buffer. */
- /* */
- /* Returns: Pointer to buffer struct or NULL upon failure. */
- /* */
- /**********************************************************************/
-
- CBUF *cbuf_malloc(CSIZE size)
- {
- CBUF *ptr;
-
-
- if (NULL != (ptr = calloc(sizeof(CBUF) + (size * sizeof(short)), 1)))
- {
- ptr->buf = (short *)((char *)ptr + sizeof(CBUF));
- ptr->len = size;
- return ptr;
- }
- else return NULL;
- }
-
- /**********************************************************************/
- /* */
- /* cbuf_add() - Function to add data to a circular buffer. */
- /* */
- /* Arguments: 1 - Pointer to circular buffer struct. */
- /* 2 - Data to add. */
- /* */
- /* Returns: TRUE if buffer has been filled, else FALSE. */
- /* */
- /**********************************************************************/
-
- LOGICAL cbuf_add(CBUF *cbuf, short data)
- {
- cbuf->buf[cbuf->next]= data;
- if (cbuf->len <= ++(cbuf->next))
- {
- cbuf->next = 0;
- cbuf->full = TRUE;
- }
- return cbuf->full;
- }
-
- /**********************************************************************/
- /* */
- /* OlympicFilt() - Function to perform an "Olympic" filter on the */
- /* data in a circular buffer. */
- /* */
- /* Arguments: 1 - Pointer to circular buffer struct. */
- /* */
- /* Returns: TRUE if buffer has been filled, else FALSE. */
- /* */
- /**********************************************************************/
-
- int OlympicFilt(CBUF * cbuf)
- {
- size_t i;
- long accum;
- short cmin = SHRT_MAX, cmax = SHRT_MIN;
- short obuf[CSIZ_HUGE * sizeof(short)];
-
-
- /*
- ** The buffer may be subject to asynchronous modification,
- ** so take a snapshot of it.
- */
-
- memcpy(obuf, cbuf->buf, cbuf->len * sizeof(short));
-
- for (i = 0, accum = 0L; i < cbuf->len; ++i)
- {
-
- accum += obuf;
- cmin = MIN(obuf[i], cmin);
- cmax = MAX(obuf[i], cmax);
- }
- accum -= cmin;
- accum -= cmax;
- switch (cbuf->len)
- {
- case CSIZ_TINY:
- return (short)(accum >> 1);
-
- case CSIZ_SMALL:
- return (short)(accum >> 2);
-
- case CSIZ_MEDIUM:
- return (short)(accum >> 3);
-
- case CSIZ_LARGE:
- return (short)(accum >> 4);
-
- case CSIZ_HUGE:
- return (short)(accum >> 5);
-
- default:
- return(short)(accum / (cbuf->len - 2));
- }
- }
-
- /**********************************************************************/
- /* */
- /* AverageFlit() - Function to filter the data in a circular buffer */
- /* by simple averaging. */
- /* */
- /* Arguments: 1 - Pointer to circular buffer struct. */
- /* */
- /* Returns: TRUE if buffer has been filled, else FALSE. */
- /* */
- /**********************************************************************/
-
- short AverageFilt(CBUF * cbuf)
- {
- size_t i;
- long accum;
- short obuf[CSIZ_HUGE * sizeof(short)];
-
- /*
- ** The buffer may be subject to asynchronous modification,
- ** so take a snapshot of it.
- */
-
- memcpy(obuf, cbuf->buf, cbuf->len * sizeof(short));
-
- for (i = 0, accum = 0L; i < cbuf->len; ++i)
- accum += obuf[i];
-
- return(short)(accum / (cbuf->len));
- }
-
- #ifdef TEST
-
-
- #include <stdio.h>
-
- int main(int argc, char *argv[])
- {
- int i, n;
- CBUF *cbuf = cbuf_malloc(CSIZ_SMALL);
-
- while (--argc)
- {
- n = atoi(*++argv);
- printf("Adding %d returned %d\n", n, cbuf_add(cbuf, n));
- }
- printf("\nOlympic average is %d\n", OlympicFilt(cbuf));
- printf("\nStraight average is %d\n", AverageFilt(cbuf));
- return EXIT_SUCCESS;
- }
-
- #endif
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- When the "Best" Algorithm Isn't
-
-
- Blase B. Cindric
-
-
- Currently in his tenth year of teaching computer science at the undergraduate
- level, Blase has spent the past six years as a faculty member at Westminster
- College, New Wilmington, PA. In addition to his full-time teaching
- responsibilities, he is a part-time Ph.D. student in computer science at the
- University of Pittsburgh, where his areas of interest are theory of
- computation and algorithmic analysis. He holds a Masters degree in computer
- science from Penn State, lives very happily with his wife, and is at the beck
- and call of their two cats. He can be reached via e-mail as
- bbc@cheese.westminster.edu.
-
-
-
-
- Introduction
-
-
- When computer scientists analyze the performance of algorithms, they attempt
- to make the analysis as general as possible by focusing on large data sets.
- Also, no assumptions are made concerning any properties that the data might
- have. The idea behind this approach is to make the resulting rankings of the
- algorithms valid for the largest variety of data possible.
- So, for instance, the consensus of the computer science community is that the
- best sorting algorithm, on average, is the Quicksort algorithm. A programmer
- without much experience who sees such a claim is likely to accept the wisdom
- of the ancients as the whole truth and nothing but the truth. But there is a
- catch. We programmers are always working on some specific problem, and
- sometimes we have extra information about our data sets that may allow us to
- obtain better performance than the "best" algorithm. For our purposes, we can
- do better than the "best."
- Such a situation arose in an application I developed several years ago. I
- present here a simplified version. We have an array that must contain unique
- values (no duplicates allowed), with the currently used positions of the array
- containing sorted values. After the end of this sorted data, several unsorted
- additions are made to the array.
- Our task is to sort the array so the entire data set is sorted. So we have an
- array that is mostly sorted, with some unsorted stuff at the end. In this
- situation, it turns out that there is a sorting method that outperforms
- Quicksort for some array configurations, a variant of the insertion sort
- routine.
-
-
- When Simpler is Better
-
-
- Fans of algorithmic analysis may balk at this claim. "How can lowly insertion
- sort, whose average behavior is O(n2), outperform the mighty Quicksort
- routine, with its O(n*log n) average time?" The secret lies in the fact that
- if we treat the array as two subarrays, one part sorted and the other part
- containing data to be inserted in order into the sorted part, then our
- algorithm need only perform a few insertions into the sorted part to
- accomplish our sorting task.
- Quicksort will process the entire array, without exploiting the fact that most
- of the sorting task is already done. For certain proportions of the size of
- the sorted and unsorted portions of the array, this insertion sort method
- outperforms Quicksort.
- Listing 1 contains source code for this special insertion sort routine. This
- code assumes that the array contains unsigned integers, and is to be sorted in
- ascending order. The idea is simple. For each element in the unsorted portion
- of the array, first copy the value into a temporary variable. Then work
- backwards through the sorted portion of the array, copying each value that is
- larger than the item to be inserted down one slot in the array.
- When control exits the inner for loop, list[j] is the array element that
- contains the largest value in the array that is not larger than the item to be
- inserted, and list[j+1] has been copied into list[j+2], with all subsequent
- values following in sequence. In essence, the code has moved all of the values
- greater than our item down one position in the array, opening up a slot
- (1ist[j+1]) where that value should be placed to maintain sorted order.
-
-
- Performance
-
-
- This special insertion sort routine performs best when the number of
- insertions to be made is small. As the size of the unsorted portion of the
- array grows relative to the sorted portion, the performance advantage
- decreases. Eventually, for a certain number of unsorted additions, the time
- taken to painstakingly move each unsorted value into its rightful place
- exceeds the time needed if we ignore our special array properties and just use
- Quicksort. This crossover point depends in each case on the sizes of the two
- portions.
- I've run tests for different array sizes and proportions of sorted to unsorted
- elements, with the results shown in Table 1. Roughly speaking, if the number
- of unsorted elements to be inserted is less than 2 percent of the size of the
- sorted portion, then this special sort method gives us a performance advantage
- over blindly using Quicksort. The solution I employed for my application
- calculates this proportion, and uses this special insertion sort technique if
- the number of additions is below this 2 per cent threshold. It uses Quicksort
- if there are lots of additions to be made.
- The whole point here is that knowledge of special characteristics of the data
- set to be processed can alter the choice of algorithm that yields the best
- performance in practice. The moral of this story? Take advantage of any
- special properties you know about the data set when choosing an algorithm, and
- you too can do better than the "best."
- Table 1 Sizes of sorted and unsorted portions of arrays. For each table entry,
- a smaller unsorted portion results in the special insertion sort outperforming
- Quicksort, while a larger unsorted portion makes Quicksort the better
- performer.
- Size of Size of
- Sorted Unsorted
- Size of Array Portion Portion Proportion
- --------------------------------------------
- 500 489 11 2.25%
- 1000 979 21 2.15%
- 2000 1952 48 2.46%
- 5000 4900 100 2.04%
- 7500 7368 132 1.79%
- 10000 9812 188 1.92%
- 15000 14692 308 2.10%
- 20000 19564 436 2.23%
-
- Listing 1 Special insertion sort routine
- /*-----------------------------*
- * Special Insertion Sort *
- * (c) 1994, Blase B. Cindric *
- * All rights reserved, and *
- * jealously guarded *
- *-----------------------------*/
-
-
- void special_insertion_sort(
-
- unsigned *list, /* array to be sorted*/
- int n, /* no, of elements in array */
- int num_sorted) /* no. of elements already in order */
-
- {
-
- int j, k;
- unsigned item_to_place;
-
- /*------------------------------------------*
- * subscripts for sorted portion of array: *
- * 0 to (num_sorted - 1) *
- * subscripts for unsorted portion: *
- * num_sorted to (n - 1) *
- *------------------------------------------*/
-
- for (k = num_sorted; k < n; k++) {
-
- /* move new item out of array */
-
- item_to_place = list[k];
-
- /* copy all values larger than new item down one place in the array */
-
- for (j = k - 1; list[j] > item_to_place && j >= 0; j--)
- list[j+1] = list[j];
-
- /* place new item in its proper array position */
-
- list[j+1] = item_to_place;
-
- } /* end of outer for loop */
-
- } /* end of special insertion sort */
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Octree Color Quantization
-
-
- Ian Ashdown
-
-
- This article is not available in electronic form.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- An Introduction to Genetic Algorithms
-
-
- Keith Grant
-
-
- Keith Grant is a software engineering manager at Standard & Poor's Compustat,
- a provider of financial data and analysis tools. He also teaches C++ and OO
- analysis and design. He has a BS in physics from the University of Washington.
-
-
-
-
- Introduction
-
-
- A surprising number of everyday problems are difficult to solve by traditional
- algorithms. A problem may qualify as difficult for a number of different
- reasons; for example, the data may be too noisy or irregular; the problem may
- be difficult to model; or it may simply take too long to solve. It's easy to
- find examples: finding the shortest path connecting a set of cities, dividing
- a set of different tasks among a group of people to meet a deadline, or
- fitting a set of various sized boxes into the fewest trucks. In the past,
- programmers might have carefully hand crafted a special-purpose program for
- each problem; now they can reduce their time significantly by using a genetic
- algorithm.
-
-
- What are Genetic Algorithms?
-
-
- A genetic algorithm (GA) is one of a relatively new class of stochastic search
- algorithms. Stochastic algorithms are those that use probability to help guide
- their search. John Holland developed GAs at the University of Michigan in the
- mid-1970s. As the name implies, GAs behave much like biological genetics. GAs
- encode information into strings, just as living organisms encode
- characteristics into strands of DNA. (The choice of the term string is
- unfortunate. In the GA community, a string contains the potential solution and
- bears no relationship to a string in C or C++.)
- A string in a GA is analogous to a chromosome in biology. A population of
- strings competes and those strings that are the fittest procreate, the rest
- eventually die off, childless. As with biological parents, two strings combine
- and contribute part of their characteristics to create their offspring, the
- new individual. This new string joins the population and the fight to produce
- the next generation. If both parents contribute good building blocks (short
- sections of the string) to the offspring, it will be more fit and will
- procreate in its turn. If the building blocks are poor then the offspring will
- die off without generating offspring. A second -- but important -- process
- occurs in GAs: sometimes, very rarely, a mutation occurs and the offspring
- will incorporate a new building block that came from neither parent. The above
- cycle of death and birth repeats until an acceptable solution to the problem
- is found.
-
-
- Benefits of Genetic Algorithms
-
-
- One of a GA's most important qualities is its ability to evaluate many
- possible solutions simultaneously. This ability, called implicit parallelism,
- is the cornerstone of GA's power. Implicit parallelism results from
- simultaneous evaluation of the numerous building blocks that comprise the
- string. Each string may contain millions of these building blocks, and the GA
- assesses them all simultaneously each time it calculates the string's fitness.
- In effect, the algorithm selects for patterns inside the string that exhibit
- high worth, and passes these building blocks on to the next generation. This
- selection process enables genetic algorithms to perform well where traditional
- algorithms flounder, such as in problems with huge search spaces.
-
-
- Robustness
-
-
- Genetic algorithms also have the quality of robustness. That is, while
- special-case algorithms may find more optimal solutions to specific problems,
- GAs perform very well over a large number of problem categories. This
- robustness results in part because genetic algorithms usually apply their
- search against a large set of points, rather than just a single point, as do
- calculus-based algorithms. Because of this, GAs are not caught by local minima
- or maxima. Another contribution to their robustness is that GAs use the
- strings' fitness to direct the search; therefore they do not require any
- problem-specific knowledge of the search space, and they can operate well on
- search spaces that have gaps, jumps, or noise.
-
-
- Miscellaneous Benefits
-
-
- GAs also perform well on problems whose complexity increases exponentially
- with the number of input parameters. Such problems, called NP-complete, would
- take years to solve using traditional approaches. Furthermore, genetic
- algorithms can produce intermediate solutions; the program can stop at any
- time if a suboptimal solution is acceptable. Finally, GAs easily lend
- themselves to parallel processing; they can be implemented on any
- multiprocessor architecture.
-
-
- The Algorithm Explored
-
-
- As the pseudo-code in Figure 1 illustrates, the basic genetic algorithm is
- quite simple.
- While extensive research on GAs has produced no optimal implementation (many
- aspects of GAs are still debated), there are several algorithms that work
- quite well in most situations. The example algorithm (Figure 1) is one of
- these implementations, and it is simple and reliable.
- Even with an off-the-shelf GA, the programmer still faces two significant
- tasks: designing the coding scheme and creating the fitness function. The
- coding scheme defines how a string will represent a potential solution of the
- problem at hand. The fitness function uses the coding scheme to evaluate each
- string's fitness or worth. By combining these two parts the genetic algorithm
- can calculate how well any string solves the problem.
-
-
- The Knapsack Problem
-
-
- To understand how GAs work, consider a concrete example. The knapsack problem
- is a classic NP-complete problem. (Sedgewick uses this kind of problem in his
- book, Algorithms [1], to illustrate dynamic programming.) Simply stated, given
- a pile of items that vary in weight and value, find that combination of items
- having the greatest total value but which does not exceed a maximum weight. In
- other words, the goal is to fill up a hypothetical knapsack with the most
- expensive loot it can carry. While it's easy to describe, this goal can be
- difficult to accomplish. For example, a pile of just 50 items presents 250
- different possible selections. Assuming a computer could test a million
- different combinations each second, it would still take 35 years to try them
- all.
- In this article, I show how a GA solves such a problem, but for the sake of
- illustration I use a smaller number of items. The pile contains fourteen
- items, so it provides a little more than 16,000 possible combinations to try
- in the knapsack. There are five different kinds of items, ranging from 3 to 9
- in weight and from 4 to 13 in value. The knapsack can hold a maximum weight of
- 17, so it can carry one A, or two Bs, etc. Table 1 lists all the different
- items, their weights and values, and maximum number of each type that can fit
- into the knapsack. The program in Listing 1 illustrates the use of a GA to
- solve the knapsack problem. Supporting functions and class definitions appear
- in Listing 2 through Listing 7.
-
-
-
- Developing a Coding Scheme
-
-
- The first step in writing a GA is to create a coding scheme. A coding scheme
- is a method for expressing a solution in a string. Many successful types have
- been discovered, but there is no mechanical technique for creating one. Like
- programming, creating a coding scheme is part science and part art, but also
- like programming, it gets easier with practice. Early researchers used binary
- encoded strings exclusively, but higher order alphabets work without loss of
- efficiency and power. The type of coding scheme to use depends on the problem.
- Order defines the number of different characters in the alphabet. Do not
- confuse the GA term character with ASCII characters. A GA character is
- analogous to a gene in that it has a position and a value. A binary alphabet
- has an order of two, meaning that the characters can only have two values, 0
- or 1.
- The coding scheme I've chosen for the knapsack uses a fixed, length, binary,
- position-dependent string. The pile in the example contains fourteen items so
- each string must have fourteen binary characters, one character for each item.
- The location of each character in the string represents a specific item and
- the value of the character indicates whether that item is in the knapsack or
- left in the pile.
- Figure 2 illustrates the coding of fourteen items into a GA string. The coding
- scheme's equivalent in C++ is the array of struct, ItemDesc, shown in Listing
- 1. Each column of the table represents a character position in the string. The
- top three lines give the label, weight, and value of each character position.
- The bottom three lines show strings that define potential solutions to the
- knapsack problem. In this case a 1 means the item is in the knapsack and a 0
- means the item is in the pile. The first string places six items into the
- knapsack: one A, B, C, and D, and two Es, for a total weight of 34 and total
- value of 47. The second string places five items in the knapsack: two Ds, and
- three Es, for a weight of 17 and value of 22. The third string uses just two
- items: one A and one E for a weight of 12 and a value of 17.
-
-
- Creating a Fitness Function
-
-
- The next step is to create a function that will evaluate how well each string
- solves the problem -- that is, calculate the string's fitness. The knapsack
- problem requires maximization of the loot's value in the knapsack. If this
- were the only requirement, a fitness function could simply rank a string by
- adding up the values of all the items put into the knapsack. The GA would then
- tell us that the best solution was to put all 14 items in the knapsack.
- However, a second requirement states that the weight of the items cannot
- exceed a maximum (17, in this example). So this fitness function fails
- miserably. In GA terminology, it results in a constraint violation.
- GA researchers have explored many approaches to constraint violation but none
- are perfect. Here are three possibilities:
-
-
- Elimination
-
-
- Elimination attempts to determine if a string violates the constraint before
- it is ever created. This approach has several problems. For starters, it may
- be too expensive to perform, or simply impossible. Second, preventing the
- creation of violators may cause GA to overlook perfectly valid solutions.
- That's because violators could produce legal (non-violating) offspring that
- would lead to a satisfactory solution more quickly.
-
-
- High Penalty
-
-
- This approach imposes a high penalty on violators. It reduces violators' worth
- while allowing them to occasionally propagate offspring. A weakness of this
- approach becomes apparent when a population contains a large percentage of
- violators. In this case, legal strings will dominate the following generations
- and the violators will be left unexploited. This effect could lead to
- population stagnation.
-
-
- Moderate Penalty
-
-
- This approach imposes a moderate penalty on violators. It increases the
- probability that violators will procreate, thus reducing the chance of
- population stagnation. This approach exhibits its own problems, especially
- when violators rate higher than legal strings. In this case, if the violators
- do not create legal strings then violators will dominate the following
- generations. Furthermore, if violators rate higher than legal strings then the
- criteria for ending the search must incorporate a mechanism for detecting
- violators.
- The knapsack example employs the third technique. Its fitness function
- (CalcFitness in Listing 1) adds up the value of each item and subtracts a
- moderate penalty for violators. The penalty is three times the amount of
- excess weight. Table 2 shows the resulting fitness of the three example
- strings previously defined.
-
-
- Initialization
-
-
- After the coding scheme and fitness function are integrated into the GA it is
- ready to run. The GA's first task is to create an initial population of
- strings. The demo program stores this population in an object (Pop) of class
- CGAPopulation (defined in Listing 4). Each string in the population is an
- object of class CGAChromosome (Listing 2).
- There are many ways to select an initial population; approaches range from
- randomly setting each character of every string to modifying the results of a
- search made previously by a human. The knapsack example uses a modified
- weighted random design. The initialization function (class CGAPopulation's
- constructor, Listing 5) creates strings with an increasing likelihood of
- setting each bit to 1. Figure 3 shows the result of creating ten strings. The
- probability of setting any bit to 1 for the first string, labeled U, is 10%.
- The probability increases incrementally for each new string created until all
- the strings are created and the probability reaches about 50%.
- After creating and initializing each string, the constructor creates a
- complement of that string, by calling member function Complement (Listing 3).
- The complement string has the opposite bit pattern of the original.
- Note that in the top half of the table the U string contains only one one-bit,
- whereas each successive string has an increasing number of one-bits, until the
- fifth string has about half ones and zeros. The bottom half of the figure
- shows the complement strings to the original five.
- The composition of the initial population can dramatically affect the
- performance of the genetic algorithm. The more diverse the initial population
- the more opportunities the GA will have to exploit the search space. The above
- initialization scheme has the advantage of simplicity and diversity. It is
- simple in that it does not require any information about the problem. The
- scheme is diverse because the function creates strings ranging from mostly
- zeros to mostly ones and everything in-between.
- How large should the initial population be? The population should be large
- enough to create a diverse set of individuals for the GA to exploit but not so
- large that creating the initial population dominates computer time. The
- knapsack example sets the initial population to 30. I picked this rather small
- population to better illustrate how GAs work.
-
-
- Parent Selection and Procreation
-
-
- After creating an initial population, the GA selects two parents for the
- purpose of procreation. Parent selection is based on string fitness. While
- creating the initial population, the fitness function calculates the worth of
- each string. This calculation occurs within each string's constructor,
- CGAChromosome::CGAChromosome (Listing 3). The population constructor
- CGAPopulation::CGAPopulation then ranks the strings according to their
- fitness, by calling member function Merge (Listing 5).
- After the popuplation constructor returns, the main program enters a while
- loop, and stays there until a solution is found. It's unlikely that any
- strings in the initial generation contain a solution; if the while condition
- is satisfied (no solution found), the program calls
- CGAPopulation::CreateNextGeneration (Listing 5) to create a new generation of
- strings.
- The first step in creating a new generation is selection of two parents. The
- GA does not select strings directly by their rank in the population, so the
- best string is not guaranteed to be a parent. Instead, the string's worth,
- based on its rank in the population as a whole, biases the probability of that
- string being selected to parent the next generation. If a string ranks as the
- 25th best out of 100 strings, then it has a 75% chance of becoming a parent.
- A GA's method of selecting parents is very important; it can significantly
- impact the efficiency of the search. Among the many types of selection
- functions, the two most widely used techniques are proportional selection and
- linear rank selection. The knapsack example uses linear rank selection, first
- because it is easy to implement (see CGAPopulation::GetParent, Listing 5).
- More important, I suspect that linear rank selection is inherently better
- behaved than proportional selection, because proportional selection has
- required many fixes to its original design over the years.
- Linear rank selection simply calculates the fitness of a string and then ranks
- it in the entire population. This process involves two random operations.
- First, GetParent randomly selects a candidate string from the population:
- . . .
- Selection=
- Rand0UpTo1();
- . . .
- Next, GetParent determines if the candidate string will parent an offspring,
- by performing a weighted probability (via function Flip, Listing 7) based on
- the string's rank. If the string does not qualify as a parent, then GetParent
- repeats the cycle and randomly selects another string from the population.
-
-
-
- Procreation
-
-
- Once two parents have been selected, the GA combines them to create the next
- generation of strings. The GA creates two offspring by combining fragments
- from each of the two parents. The knapsack example uses uniform crossover to
- cut up fragments from the parents (see function Crossover, Listing 3). The
- positions where strings are cut into fragments are called the crossover
- points. Crossover chooses these points at random; uniform crossover means that
- every point has an equal chance of being a crossover point.
- Crossover selection occurs via a crossover mask. Figure 4 illustrates the use
- of a crossover mask. The first child will receive the first parent's character
- if the bit is 1 and the second parent's character if the bit is 0. The second
- child works in reverse.
- Uniform crossover implies many crossover points with an even probability
- distribution across the entire length of each parent string. The fragments
- from the first parent combined with their complementary members from the
- second parent creates two new strings.
-
-
- Mutation
-
-
- Sometimes the children undergo mutation. The knapsack example uses an
- interesting mutation operator (see CGAChromosome::Mutate, Listing 3). Rather
- than a fixed mutation probability, Mutate uses a probability that changes
- based on the makeup of the population. Mutate compares the two parents of the
- child; greater similarity between parents increases the probability that a
- mutation will occur in the child. The reason for using a variable mutation
- probability is to reduce the chance of premature convergence. This condition
- occurs when the population rushes to a mediocre solution and then simply runs
- out of steam. There is little diversity left and the search becomes a random
- walk among the average. This is similar to a biological species becoming so
- inbred that it is no longer viable. To reduce premature convergence the
- mutation operator kicks in when the population shows little diversity and adds
- new variety by introducing random mutation.
-
-
- Finding the Answer
-
-
- The two children now can replace two older strings from the population. This
- occurs in function CGAPopulation::ReplaceChromosome (Listing 5). As it does
- with parent selection, the GA chooses these older strings with a random bias.
- In this case, however, the worst string will have the greatest chance of being
- removed. After the insertion of new strings, the population is then ranked
- again. The program stops when any string solves the problem.
- This raises a question: if the best solution is unknown, how can the program
- determine if a it has found the best answer, or at least, one of the better
- answers? One approach would be to solve for a fixed solution that meets some
- predetermined minimally acceptable value. A second approach would be to run
- the program until its rate of finding better answers drops off or the rate of
- improvement of those answers flattens out.
- The knapsack example ran until it found the known answer, which is 24. It
- took, on average, about 160 generations to find the solution -- about 350 out
- of 16,000 possibilities, or 2% of the search space. Indeed, this problem is
- small enough to solve with traditional methods, but by watching how the code
- operates in detail you can get a good idea of how GAs work. The example is
- quite small and expandable. You can try it on different problems simply by
- creating a new ItemDesc structure and the related CalcFitness function. All
- the I/O is confined to the PrintPop function (Listing 1), so you could easily
- drop the example into a larger program.
-
-
- Conclusion
-
-
- Genetic algorithms balance exploitation with exploration. The crossover and
- mutation operators control exploration while the selection and fitness
- functions control exploitation. Increasing exploitation decreases exploration.
- Mutation increases the ability to explore new areas of the search space but it
- also disrupts the exploitation of the previous generations by changing them.
- Genetic algorithms represent a new, innovative approach to search algorithms.
- Unlike most traditional algorithms, GAs are not deterministic, rather they
- exploit the power of probabilistic operations. By definition and design they
- are adaptive. Survival of the fittest governs the progress of the search, and
- with the possibility of mutations, GAs may explore completely unexpected
- avenues. GAs exhibit a chaotic search behavior very similar to how humans
- conduct searches -- part analytical, part intuition, and part luck.
-
-
- Bibliography
-
-
- [1] Sedgewick, Robert. Algorithms, 2nd ed. Addison-Wesley, 1988.
- [2] Holland, H. Adaptation in Natural and Artificial Systems. The University
- of Michigan Press, 1975.
- [3] Schaffer, David. Proceedings of the Third International Conference on
- Genetic Algorithms. Morgan Kaufmann, 1989.
- [4] Goldberg, David E. Genetic Algorithms in Search, Optimization, and Machine
- Learning. Addison-Wesley, 1989.
- [5] Levy, Steven. Artificial Life: the Quest for a New Creation. Pantheon
- Books, 1992.
- [6] Davis, Lawrence. Research Notes in Artificial Intelligence, Genetic
- Algorithms, and Simulated Annealing. Morgan Kaufmann, 1987.
- Figure 1 Pseudo code of the genetic algorithm
- Create an initial population of strings.
- Calculate the fitness of each string.
- WHILE an acceptable solution is not found.
- Select parents for the next generation.
- Combine the parents to create new offspring.
- Calculate the fitness of each offspring.
- END WHILE.
- Figure 2 The coding scheme for the knapsack example
- A B B C C D D D D E E E E E Label
- 9 8 8 7 7 4 4 4 4 3 3 3 3 3 Weight
- 13 11 11 10 10 5 5 5 5 4 4 4 4 4 Value
-
- Strings Wt Value
-
- 1 0 1 1 0 0 0 0 1 1 1 0 0 0 34 47
- 0 0 0 0 0 1 1 0 0 1 1 0 1 0 17 22
- 1 0 0 0 0 0 0 0 0 0 0 0 1 0 12 17
- Figure 3 Initializing a population of ten strings
-
- Strings 1s Count Prob.
-
- U 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 10%
- W 0 0 1 0 0 0 1 0 0 0 0 0 0 0 2 20%
- X 0 1 1 1 0 0 0 0 0 1 1 0 0 1 6 30%
- Y 0 0 1 1 0 1 1 0 0 1 1 0 0 0 6 40%
- X 1 1 0 1 0 1 0 1 1 0 1 1 0 0 8 50%
-
- String's complement
-
- ~U 0 1 1 1 1 1 1 1 1 1 1 1 1 1
- ~W 1 1 0 1 1 1 0 1 1 1 1 1 1 1
- ~X 1 0 0 0 1 1 1 1 1 0 0 1 1 0
- ~Y 1 1 0 0 1 0 0 1 1 0 0 1 1 1
- ~X 0 0 1 0 1 0 1 0 0 1 0 0 1 1
-
- Figure 4 The uniform crossover operator
- Parent 1 1 1 0 0 0 1 0 0 0 1 1 1 0
- Parent 2 1 0 1 1 1 1 1 1 0 0 1 1 0
- Mask 1 0 0 1 1 0 1 0 0 0 0 1 1
- Child 1 1 0 1 0 0 1 0 1 O 0 1 1 O
- Child 2 1 1 0 1 1 1 1 0 0 1 1 1 0
- Table 1 Description of the knapsack items
- Label A B C D E
- --------------------------
- Weight 9 8 7 4 3
- Value 13 11 10 5 4
- Quantity 1 2 2 4 5
- Table 2 The results of the fitness function using a moderate penalty
- Weight 34 17 12
- Value 47 22 17
- Fitness -4 22 17
-
- Listing 1 Demo program and global functions to solve knapsack problem
- #include <stdlib.h>
- #include <stdio.h>
- #include "pop.h"
-
- static enum Bool {FALSE, TRUE};
-
- // MAXWEIGHT defines the constraint of the knapsack
- // example and ItemDesc is simply the coding scheme.
-
- static const int MAXWEIGHT = 17;
- static const struct
- {
- int Weight;
- int Value;
- } ItemDesc[] = {
- {3, 4}, {3, 4}, {3, 4}, {3, 4}, {3, 4},
- {4, 5}, {4, 5}, {4, 5}, {4, 5},
- {7, 10}, {7, 10}, {8, 11}, {8, 11}, {9, 13}};
-
- void CalcWeightAndValue(CGAChromosome *Chromosome,
- int& Weight, int& Value);
- Bool FoundSolution(CGAPopulation& Pop);
- void PrintPop(CGAPopulation& Pop);
-
- // Main creates the population of chromosomes and
-
- // continues creating new generations until a solution
- // is found.
-
- int main(void)
- {
- const float MaxMutationRate = 0.2;
- const int PopulationSize = 30;
-
- CGAPopulation Pop(PopulationSize, sizeof(ItemDesc) /
- sizeof(ItemDesc[0]),
- MaxMutationRate);
-
- while (!FoundSolution(Pop)) {
- Pop.CreateNextGeneration();
- PrintPop(Pop);
- }
- return EXIT_SUCCESS;
- }
-
- // Print information and statistics about population.
-
- void PrintPop(
- CGAPopulation& Pop)
- {
- float TotalFitness = 0;
-
- printf("Idx Fit Val Wt Chromosome\n");
- for (size_t ChromIdx = 0;
- ChromIdx < Pop.GetPopulationSize();
- ChromIdx++) {
-
- int Weight;
- int Value;
- CGAChromosome *Chromosome =
- Pop.GetChromosome(ChromIdx);
-
- TotalFitness += Chromosome->GetFitness();
- CalcWeightAndValue(Chromosome, Weight, Value);
- printf("%3u %4.0f %3d %3d ",
- ChromIdx, Chromosome->GetFitness(),
- Value, Weight);
- for (size_t BitIdx = 0;
- BitIdx < Chromosome->GetLength();
- BitIdx++) {
- printf("%1u", (*Chromosome)[BitIdx]);
- }
- printf("\n");
- }
- printf(
- "Gen, Best, Avg, Worst: %4d, %6.2f, %6.2f, %6.2f\n",
- Pop.GetGeneration(), Pop.GetBestFitness(),
- TotalFitness / ChromIdx,
- Pop.GetChromosome(ChromIdx - 1)->GetFitness());
- getchar();
- }
-
- // Check if a solution has been found. By definition
- // it must have a value of ANSWER (24) and not exceed
- // MAXWEIGHT (17). Since the fittest chromosome could
-
- // violate the weight constraint FoundSolution must
- // search through the population of chromosomes.
-
- Bool FoundSolution(
- CGAPopulation& Pop)
- {
- const int ANSWER = 24;
- int Weight;
- int Value;
-
- for (size_t ChromIdx = 0;
- ChromIdx < Pop.GetPopulationSize();
- ChromIdx++) {
- CalcWeightAndValue(Pop.GetChromosome(ChromIdx),
- Weight, Value);
- if (Weight <= MAXWEIGHT && Value == ANSWER) {
- return TRUE;
- }
- }
- return FALSE;
- }
-
- // Calculate the fitness of each chromosome by adding
- // its weight to its value then subtracting a PENALITY
- // for the excess weight.
-
- float CalcFitness(
- CGAChromosome *Chromosome)
- {
- const float PENALITY = 3.0;
- int Weight;
- int Value;
-
- CalcWeightAndValue(Chromosome, Weight, Value);
- if (Weight > MAXWEIGHT) {
- return Value - PENALITY * (Weight - MAXWEIGHT);
- } else {
- return Value;
- }
- }
-
- // Calculate the weight and value of the chromosome by
- // accumulating the weight and value of each item
- // whose bit in the chromosome is set true.
-
- void CalcWeightAndValue(
- CGAChromosome *Chromosome,
- int& Weight,
- int& Value)
- {
- Weight = 0;
- Value = 0;
- for (size_t Idx = 0; Idx < Chromosome->GetLength();
- Idx++) {
- if ((*Chromosome)[Idx]) {
- Weight += ItemDesc[Idx].Weight;
- Value += ItemDesc[Idx].Value;
- }
- }
-
- }
- /* End of File */
-
-
- Listing 2 Definition of class CGAChromosome and short member functions
- #include "random.h"
-
- #ifndef _INC_CHROM_____LINEEND____
- #define _INC_CHROM_____LINEEND____
-
- class CGAChromosome
- {
- public:
- static void InitializeChromosomeClass(
- size_t Length);
- ~CGAChromosome();
- CGAChromosome(float Prob = 0.0);
-
- CGAChromosome* Compliment() const;
- void Mutate(float Prob);
- inline size_t GetLength() const;
- inline float GetFitness() const;
- friend void Crossover(CGAChromosome* Parent1,
- CGAChromosome* Parent2,
- CGAChromosome*& Child1,
- CGAChromosome*& Child2,
- float Prob);
- friend float CalcSimilarityRatio(
- CGAChromosome* Chrom1,
- CGAChromosome* Chrom2);
- inline GABool& operator[](size_t Idx);
-
- private:
- static void InitializeCrossoverMask(float Prob);
-
- static GABool* m_CrossoverMask;
- static size_t m_Count;
- static size_t m_Length;
- GABool* m_Data;
- float m_Fitness;
- };
-
- size_t CGAChromosome::GetLength() const
- {
- return m_Length;
- }
-
- float CGAChromosome::GetFitness() const
- {
- return m_Fitness;
- }
-
- GABool& CGAChromosome::operator[](size_t Idx)
- {
- return m_Data[Idx];
- }
-
- float CalcFitness(CGAChromosome* Chromosome);
-
-
- #endif
- /* End of File */
-
-
- Listing 3 More member functions of class CGAChromosome
- #include <stdio.h>
- #include <stdlib.h>
- #include <assert.h>
-
- #include "chrom.h"
-
- size_t CGAChromosome::m_Count = 0;
- size_t CGAChromosome::m_Length = 0;
- GABool* CGAChromosome::m_CrossoverMask = 0;
-
- // Default constructor and destructor.
- // Create the chromosome with bits turned on at the
- // given probability.
-
- CGAChromosome::CGAChromosome(
- float Prob)
- {
- assert(m_Length);
- m_Fitness = 0;
- m_Data = new GABool[m_Length];
- assert(m_Data);
- m_Count++;
- for (size_t Idx = 0; Idx < m_Length; Idx++) {
- m_Data[Idx] = Flip(Prob);
- }
- m_Fitness = CalcFitness(this);
- }
-
- CGAChromosome::~CGAChromosome(void)
- {
- delete[] m_Data;
- if (--m_Count == 0) {
- delete[] m_CrossoverMask;
- m_Length = 0;
- m_CrossoverMask = 0;
- }
- }
-
- // Mutate a single chromosome gene selected at
- // random for a given probability.
-
- void CGAChromosome::Mutate(
- float Prob)
- {
- for (size_t Idx = 0; Idx < m_Length; Idx++) {
- if (Flip(Prob)) {
- m_Data[Idx] = !m_Data[Idx];
- }
- }
- }
-
- // Create two new offspring using a uniform crossover
- // operator created with the given probability.
-
-
- void Crossover(
- CGAChromosome* Parent1,
- CGAChromosome* Parent2,
- CGAChromosome*& Child1,
- CGAChromosome*& Child2,
- float Prob)
- {
- CGAChromosome::InitializeCrossoverMask(0.5);
- Child1 = new CGAChromosome();
- Child2 = new CGAChromosome();
- assert(Child1 && Child2);
-
- for (size_t Idx = 0; Idx < CGAChromosome::m_Length;
- Idx++) {
- if (CGAChromosome::m_CrossoverMask[Idx]) {
- Child1->m_Data[Idx] = Parent1->m_Data[Idx];
- Child2->m_Data[Idx] = Parent2->m_Data[Idx];
- } else {
- Child1->m_Data[Idx] = Parent2->m_Data[Idx];
- Child2->m_Data[Idx] = Parent1->m_Data[Idx];
- }
- }
- Child1->Mutate(Prob);
- Child2->Mutate(Prob);
- Child1->m_Fitness = CalcFitness(Child1);
- Child2->m_Fitness = CalcFitness(Child2);
- }
-
- // Calculate the difference between two chromosomes
-
- float CalcSimilarityRatio(
- CGAChromosome* Chrom1,
- CGAChromosome* Chrom2)
- {
- for (size_t Idx = 0, MatchCount = 0;
- Idx < CGAChromosome::m_Length; Idx++) {
- if (Chrom1->m_Data[Idx] == Chrom2->m_Data[Idx]) {
- MatchCount++;
- }
- }
- return (float)MatchCount /
- (float)CGAChromosome::m_Length;
- }
-
- // Set the new chromosome to the opposite encoding
- // of this chromosome.
-
- CGAChromosome* CGAChromosome::complement(void) const
- {
- CGAChromosome* Chromosome = new CGAChromosome;
- for (size_t Idx = 0; Idx < m_Length; Idx++) {
- Chromosome->m_Data[Idx] = !m_Data[Idx];
- }
- Chromosome->m_Fitness = CalcFitness(Chromosome);
- return Chromosome;
- }
-
- // Setup the crossover mask for creating the next
- // offspring.
-
-
- void CGAChromosome::InitializeCrossoverMask(
- float Prob)
- {
- assert(m_Length && m_CrossoverMask);
- for (size_t Idx = 0; Idx < m_Length; Idx++) {
- m_CrossoverMask[Idx] = Flip(Prob);
- }
- }
-
- // Setup the chromosomes' length and allocate memory
- // for the crossover mask.
-
- void CGAChromosome::InitializeChromosomeClass(
- size_t Length)
- {
- assert(Length);
- m_Length = Length;
- if (m_Count) != 0) {
- delete [] m_CrossoverMask;
- }
- m_CrossoverMask = new GaBool[m_Length];
- assert(m_CrossoverMask);
- }
- /* End of File */
-
-
- Listing 4 Definition of class CGAPopulation and short member functions
- #include "random.h"
- #include "chrom.h"
-
- #ifndef _INC_POP_____LINEEND____
- #define _INC_POP_____LINEEND____
-
- class CGAPopulation
- {
- public:
- ~CGAPopulation();
- CGAPopulation(size_t Size, size_t Length,
- float MaxMutationRate);
-
- void CreateNextGeneration(void);
- inline float GetBestFitness(void);
- inline size_t GetGeneration(void);
- inline size_t GetPopulationSize(void);
- inline CGAChromosome* GetChromosome(size_t Idx);
-
- private:
- CGAChromosome* GetParent();
-
- void ReplaceChromosome(CGAChromosome* Chromosome);
- void Merge(CGAChromosome* NewChromosome);
-
- size_t m_MaxSize;
- size_t m_CurrentSize;
- CGAChromosome** m_Data;
- size_t m_Generation;
- float m_MaxMutationRate;
- };
-
-
- float CGAPopulation::GetBestFitness(void) {
- return m_Data[0]->GetFitness();
- }
-
- size_t CGAPopulation::GetGeneration(void) {
- return m_Generation;
- }
- size_t CGAPopulation::GetPopulationSize(void) {
- return m_CurrentSize;
- }
- CGAChromosome* CGAPopulation::GetChromosome(
- size_t Idx)
- {
- return m_Data[Idx];
- }
- #endif
- /* End of File */
-
-
- Listing 5 More member functions of class CGAPopulation
- #include <stdlib.h>
- #include <stdio.h>
- #include <float.h>
- #include <assert.h>
- #include <string.h>
-
- #include "pop.h"
- #include "random. h"
-
- // Constructor. It creates a population of chromosomes
- // where the probability that each bit is true
- // increases with each creation, then it creates a
- // complement of each of the created chromosomes.
-
- CGAPopulation::CGAPopulation(
- unsigned int Size,
- unsigned int Length,
- float MaxMutationRate)
- {
- assert(Size && Length);
- m_MaxSize = Size;
- m_CurrentSize = 0;
- m_Generation = 0;
- m_MaxMutationRate = MaxMutationRate;
- m_Data = new CGAChromosome*[m_MaxSize];
- CGAChromosome::InitializeChromosomeClass(Length);
-
- for (unsigned int idx = 1; idx <= m_MaxSize / 2;
- idx++) {
- CGAChromosome* Temp =
- new CGAChromosome(idx / (float)m_MaxSize);
- CGAChromosome* CompTemp = Temp->complement();
- Merge(Temp);
- Merge(CompTemp);
- }
- }
-
- // Default destructor
-
-
- CGAPopulation::~CGAPopulation()
- {
- for (unsigned int idx = 0; idx < m_MaxSize; idx++) {
- delete m_Data[idx];
- }
- delete m_Data;
- }
-
- // Merge sort the new chromosome, assume that there no
- // holes in the array.
-
- void CGAPopulation::Merge(
- CGAChromosome* NewChromosome)
- {
- assert(m_CurrentSize < m_MaxSize);
- for (unsigned int idx = 0; idx < m_CurrentSize;
- id++) {
- if (NewChromosome->GetFitness() >
- m_Data[idx]->GetFitness()) {
- break;
- }
- }
- memmove(&m_Data[idx + 1], &m_Data[idx],
- sizeof(m_Data) * (m_CurrentSize - idx));
- m_Data[idx] = NewChromosome;
- m_CurrentSize++;
- }
-
- // These functions randomly select a chromosome from
- // the ranked array, where the probability of selection
- // is related to the individual's position in the
- // array. ReplaceChromosome calculates an individual's
- // probability for replacement by Rank(X) / Total.
- // GetParent calculates an individual's probability for
- // parenthood by (Total - Rank(X)) / Total).
-
- CGAChromosome* CGAPopulation::GetParent()
- {
- float Selection;
-
- do {
- Selection = Rand0UpTo1();
- } while (Flip(Selection));
- return m_Data[(int)(Selection * m_MaxSize)];
- }
-
- // Replace a poor chromosome with the new one.
-
- void CGAPopulation::ReplaceChromosome(
- CGAChromosome* NewChromosome)
- {
- float Selection;
- do {
- Selection = Rand0UpTo1();
- } while (Flip(Selection));
-
- unsigned int idx = m_MaxSize -
- (int)(Selection * m_MaxSize) - 1;
-
- delete m_Data[idx];
- memmove(&m_Data[idx], &m_Data[idx + 1],
- sizeof(m_Data) * (m_CurrentSize - idx - 1));
- m_CurrentSize--;
- Merge(NewChromosome);
- }
-
- // Create two offspring and replace two members of the
- // chromosome array.
-
- void CGAPopulation::CreateNextGeneration(void)
- {
- CGAChromosome *Parent1, *Parent2, *Child1, *Child2;
-
- Parent1 = GetParent();
- Parent2 = GetParent();
- Crossover(Parent1, Parent2, Child1, Child2,
- CalcSimilarityRatio(Parentl, Parent2) *
- m_MaxMutationRate);
- ReplaceChromosome(Child1);
- ReplaceChromosome(Child2);
- m_Generation++;
- }
- /* End of File */
-
-
- Listing 6 random.h -- header file for probability functions
- z#ifndef _INC_GLOBAL_____LINEEND____
- #define _INC_GLOBAL_____LINEEND____
-
- typedef unsigned char GABool;
-
- float Rand0UpTo1(void);
- float Rand0To1(void);
- GABool Flip(float Prob);
-
- #endif
-
- /* End of File */
-
-
- Listing 7 Probability functions
- #include <stdlib.h>
-
- #include "random.h"
-
- // Return a pseudo-random number from 0.0 upto,
- // but not including, 1.0.
-
- float Rand0UpTo1(void)
- {
- return rand() / (float)(RAND_MAX + 1.0);
- }
-
- // Return a pseudo-random number between 0.0 and
- // 1.0 inclusive.
-
- float Rand0To1(void)
- {
-
- return rand() / (float)RAND_MAX;
- }
-
- // Return TRUE at the specified probability.
-
- GABool Flip(float Prob)
- {
- return Rand0To1() <= Prob;
- }
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Standard C/C++
-
-
- The Header <sstream>
-
-
-
-
- P.J. Plauger
-
-
- P.J. Plauger is senior editor of C/C++ Users Journal. He is convener of the
- ISO C standards committee, WG14, and active on the C++ committee, WG21. His
- latest books are The Draft Standard C++ Library, and Programming on Purpose
- (three volumes), all published by Prentice-Hall. You can reach him at
- pjp@lauger.com.
-
-
-
-
- Introduction
-
-
- The header <sstream> is a variation on the header (strstream>, which I have
- described in the previous two installments of this column. (See "Standard
- C/C++: The Header <strstream>," CUJ, January 1995, and "Standard C/C++:
- Implementing <strstream>," CUJ, February 1995.) It is designed to work more
- closely with class string. A string object controls an in-memory character
- sequence, supporting operations on the sequence such as assignment,
- concatenation, comparison, and searching.
- Like <strstream>, <sstream) defines three classes that cooperate to help you
- read and write character sequences stored in memory:
- stringbuf, derived from streambuf to mediate access to an in-memory character
- sequence and grow it on demand (yes, the name should be stringstreambuf for
- greater uniformity)
- istringstream, derived from istream to construct a stringbuf object with input
- and/or output streams and to assist in extracting from the stream
- ostringstream, derived from ostream to construct a stringbuf object with input
- and/or output streams and to assist in inserting into the stream
- If these classes sound suspiciously like the classes defined in the header
- <strstream), that is hardly an accident. A stringbuf object supports much the
- same control over input and output streams as does a strstreambuf object.
- There are just a few added capabilities:
- You can initialize the character sequence controlled by a stringbuf from the
- character sequence controlled by a string, when the stringbuf is constructed
- or repeatedly thereafter.
- You can initialize the character sequence controlled by a newly constructed
- string object from the character sequence controlled by a stringbuf.
- You can specify independently, when a stringbuf is constructed, whether the
- input stream is readable or the output stream is writable.
- Unlike a strstreambuf object, however, a stringbuf object does not let you
- muck with a character sequence that is also controlled by a string object. It
- simply facilitates copying between two representations private to each object.
- The classes istringstream and ostringstream behave much like istrstream and
- ostrstream. They construct a stringbuf object for you. They also provide
- member functions that access the special features of a stringbuf object. In
- this case, that mostly involves copying to and from string objects, as
- described above.
- A few subtle differences exist between class stringbuf and class strstreambuf.
- If an object of the latter class defines an output sequence, it always defines
- an input sequence as well. But that is not always true for an object of class
- stringbuf. Otherwise, the two kinds of stream buffers are more alike than
- different.
-
-
- Recent Changes
-
-
- As I emphasized last month, the code I present here is based on the draft C++
- Standard as of March 1994. Despite the many changes that have occurred in the
- past year, the functionality of the classes defined in <sstream) is
- essentially unchanged -- at least for the char-based streams we all know and
- love. But many headers have now been "templatized." In particular, iostreams
- operations are now mostly defined for an arbitrary "character" type T.
- To reproduce the existing functionality, the library instantiates these
- general templates for T defined as type char. The net result is much the same,
- but the machinery -- and the notation -- is now far more elaborate. I prefer
- to show the behavior of iostreams for the more familiar char-based streams.
- The library also instantiates these templates for T defined as type wchar_t.
- If you want to manipulate wide-character strings, that can be invaluable, but
- such usage is still relatively rare. Even more rare is the use of iostreams
- based on arbitrary characters of type T. (As far as I know, such usage is
- nonexistent.) It will be interesting to see how useful such generality will
- prove in future practice.
- I also reported last month that the header <strstream) has been exempted from
- the templatizing treatment. The committee is staking the future on <sstream>,
- which does much the same thing as <strstream) but works with templatized
- strings of arbitrary character types. The older header <strstream> is retained
- mostly in the interest of preserving existing code.
- Finally, I note that the member function name str has already been changed in
- class string since March 1994. It is now data. That cleanup has yet to extend
- to the classes defined in <sstream>, but it might.
-
-
- Using <sstream>
-
-
- You include the header <sstream> to make use of any of the classes
- istringstream ostringstream or stringbuf. Objects of these classes let you
- read and write in-memory character sequences just as if they were conventional
- files, and copy character sequences. You can choose among three patterns of
- access:
- read only
- write only
- read/write
- I deal with each of these options in turn. For a discussion of
- stream-positioning operations on in-memory character sequences, see the
- January 1995 installment. The issues are essentially the same.
- If all you want to do is read an inmemory character sequence that is
- initialized from a string object, construct an object of class istringstream.
- If you know at construction time what string object s you wish to use, you can
- write:
- istringstream strin(s);
- The resultant stream buffer (pointed at by strin.rdbuf()) does not support
- insertions. You can, however, replace the character sequence completely from
- another string object s2 with the call:
- strin.str(s2);
- The stream position is reset to the beginning of the stream. (And the
- resultant stream buffer still does not support insertions.)
- You can also construct an istringstream object with an empty character
- sequence, using the default constructor. Presumably, you would later supply a
- non-empty character sequence, as in:
-
- istringstream strin;
- strin.str(s);
- If all you want to do is create an in-memory character sequence to be
- eventually copied to a string object, construct an object of class
- ostringstream to control insertions into it. You can write:
- ostringstream strout;
- then insert into strout just like any other output stream. The character
- sequence can grow dynamically to arbitrary length. (The actual limit is
- usually INT_MAX, defined in <limits.h>, or when a storage allocation request
- fails.) The resultant stream buffer (pointed at by strout. rdbuf()) does not
- support extractions, by the way.
- Your goal in creating a write-only character sequence is to capture the final
- result in a string object, as a rule. Write s = strout.str() to construct a
- string object, initialize it to control a copy of the character sequence, and
- assign it to the string object s. The two character sequences can, of course,
- evolve separately thereafter.
- If you want a null-terminated string, there is no real need to insert a null
- character last. It will not be supplied for you when you call s =
- strout.str(), as above. On the other hand, you must then call s.c_str() to get
- a pointer to the beginning of the character sequence. That call will supply a
- terminating null character.
- If you want to create an in-memory character sequence that you can read as
- well as write, you need two objects to control the input and output streams.
- The classes istringstream and ostringstream are highly symmetric. Thus, you
- have three equally valid ways to do the job. If you don't want to supply an
- initial character sequence, you can write:
- istringstream istr(ios::in ios::out);
- ostream ostr(istr. rdbuf());
- or:
- ostringstream ostr(ios::in ios::out);
- istream istr(ostr. rdbuf());
- or:
- stringbuf sb(ios::in ios::out);
- istream istr(&sb);
- ostream ostr(&sb);
- All approaches cause istr to control the input stream and ostr to control the
- output stream.
- You can also supply an initial character sequence from a string object s in
- each of these three cases:
- istringstream istr(s, ios::in ios::out);
- ostream ostr(istr. rdbuf( ) );
- or:
- ostringstream ostr(s, ios::in ios::out);
- istream istr(ostr. rdbuf() );
- or:
- stringbuf sb(s, ios::in ios::out);
- istream istr(&sb);
- ostream ostr(&sb);
- Note that both the input and output stream positions are initially at the
- beginning of the character sequence. That may not be what you intend. Always
- consider whether you want to alter the output stream position before you do
- anything else with such a read/write stream.
-
-
- Implementing <sstream>
-
-
- Listing 1 shows the file sstream, which implements the standard header
- <sstream>. It defines the classes stringbuf, istringstream, and ostringstream.
- Note that class stringbuf is based on class strstreambuf, which I described
- last month. The draft C++ Standard says that stringbuf is derived directly
- from streambuf. Such indirect derivation is permitted by the library "front
- matter."
- I chose this implementation, as I hinted last month, because the two derived
- classes are so much alike. I added a bit of logic to class strstreambuf to
- close the gap:
- I added the element _Noread to the type strstreambuf ::_Strmode, to note when
- a stringbuf object does not support extractions.
- The member function strstreambuf::_Init has a default fourth argument, of type
- strstreambuf::_Strmode, to communicate extra mode information from a stringbuf
- constructor.
- The member function strstreambuf::_Tidy is a separate function, even though it
- is called only by the destructor for strstreambuf, It thus can also be called,
- to advantage, by stringbuf::str(const string&).
- I also added to class stringbuf the secret protected member function _Mode. It
- maps constructor arguments of type ios::openmode to their corresponding
- strstreambuf::_Strmode values:
- If ios::in is not set, the function sets_Strmode::_Noread in the return value.
- If ios::out is not set, the function sets _Strmode::_Constant in the return
- value.
- The effect of all this groundwork is to dramatically reduce the amount of new
- code required to implement the classes defined in <sstream>.
- Listing 2 shows the file stringbu. c, which defines the two functions required
- for practically any use of class stringbuf. These are the destructor and the
- member function stringbuf::_Mode. Class stringbuf has only two additional
- member functions not defined inline, the two flavors of str.
- Listing 3 shows the file strbstr0. c, which defines the member function
- stringbuf::str(). If an input stream exists, the complexity lies in
- determining the current extent of the character sequence. The calculation is
- reminiscent of the logic for updating the strstreambuf member object
- _Seekhigh. (See last month.)
- Listing 4 shows the file strbstr1.c, which defines the member function
- stringbuf::str(const string&). It discards any existing character sequence and
- reinitializes the stream buffer to control a copy of the new one. The
- strstreambuf secret member functions really pay off here.
- The remaining source files implement the other two classes defined in
- <sstream>.
- Listing 5 shows the file istrings. c, which defines the destructor for class
- istringstream. And Listing 6 shows the file ostrings. c, which defines the
- destructor for class ostringstream. The header supplies inline definitions for
- all other member functions in these two classes.
-
-
- Testing <sstream>
-
-
- Listing 7 shows the file tsstream. c. It tests the basic properties of the
- classes defined in <sstream>. It does so in three groups:
- stringbuf objects (t1())
- istringstream objects (t2())
- ostringstream objects (t3())
- The function main() simply performs these groups of tests in the order shown.
- If all goes well, the program prints:
- SUCCESS testing <sstream>
-
- and takes a normal exit.
- This article is excerpted in part from P.J. Plauger, The Draft Standard C++
- Library, (Englewood Cliffs, N.J.: Prentice-Hall, 1995).
-
- Listing 1 The header <sstream>
- // sstream standard header
- #ifndef _SSTREAM_____LINEEND____
- #define _SSTREAM_____LINEEND____
- #include <string>
- #include <strstream>
- // class stringbuf
- class stringbuf : public strstreambuf {
- public:
- stringbuf(ios::openmode _W = ios::in ios::out)
- : strstreambuf(0, 0, 0, _Mode(_W)) {}
- stringbuf(const string& _S,
- ios::openmode_W = ios::in ios::out)
- : strstreambuf((char*)_S.c_str( ,_S.length(), 0,
- _Mode(_W)) {}
- virtual ~stringbuf();
- string str() const;
- void str(const string& _S);
- protected:
- _Strstate _Mode(ios::openmode);
- };
- // class istrstream
- class istringstream : public istream {
- public:
- istringstream(openmode _W = in)
- : istream(&_Sb), _Sb(_W) {}
- istringstream(const string&_S, openmode _W = in)
- : istream(&_Sb), _Sb(_S, _W) {}
- virtual ~istringstream();
- stringbuf *rdbuf() const
- {return ((stringbuf *)&_Sb); }
- string str() const
- {return (_Sb.str()); }
- void str(const string& _S)
- {_Sb.str(_S); }
- private:
- stringbuf _Sb;
- };
- // class ostrstream
- class ostringstream: public ostream {
- public:
- ostringstream(openmode_W = out)
- : ostream(&_Sb), _Sb(_W) {}
- ostringstream(const string& _S, openmode _W = out)
- : ostream(&_Sb), _Sb(_S, _W) {}
- virtual ~ostringstream();
- stringbuf *rdbuf() const
- {return ((stringbuf *)&_Sb); }
- string str() const
- {return (_Sb.str());)
- void str(const string& _S)
- {_Sb.str(_S); }
- private:
- stringbuf _Sb;
- };
- #endif /* _SSTREAM_ */
-
-
-
- Listing 2 The file stringbu.c
- // stringbuf -- stringbuf basic members
- #include <sstream>
-
- stringbuf::~stringbuf()
- { // destruct a stringbuf
- }
-
- strstreambuf::_Strstate stringbuf::_Mode(ios::openmode which)
- { // map ios::openmode to _Strstate
- _Strstate mode = _Dynamic;
- if (!(which & ios::in))
- mode = _Noread;
- if (!(which & ios::out))
- mode = _Constant;
- return (mode);
- }
-
-
- Listing 3 The file strbstr0.c
- // strbstr0 -- stringbuf::str()
- #include <sstream>
-
- string stringbuf::str() const
- { // construct string from stringbuf
- if (gptr() != 0)
- return (string(eback(),
- (pptr() == 0 pptr() < egptr() ? egptr(): pptr())
- - eback( ) ) );
- else if (!(_Strmode & _Constant) && pptr() != 0)
- return (string(pbase(), pptr() - pbase()));
- else
- return (string(""));
- }
-
-
- Listing 4 The file strbstr1. c
- // strbstr1 -- stringbuf::str(const string&)
- #include <sstream>
-
- void stringbuf::str(const string& str)
- { // construct stringbuf from string
- _Tidy();
- _Init(str.length(), (char *)str.c_str(), 0, _Strmode);
- }
-
-
- Listing 5 The file istrings.c
- // istringstream -- istringstream basic members
- #include <sstream>
-
- istringstream::~istringstream()
- { // destruct an istringstream
- }
-
-
- Listing 6 The file ostrings.c
-
- // ostringstream -- ostringstream basic members
- #include <sstream>
-
- ostringstream::~ostringstream()
- { // destruct an ostringstream
- }
-
-
- Listing 7 The file tsstream.c
- // test <sstream>
- #include <cassert>
- #include <iostream>
- #include <sstream>
-
- void t1()
- { // test stringbuf
- string s0("s0"), s1("s1"), s2("s2"), s3("s3");
- stringbuf sb0, sb1(ios::in), sb2(ios::out),
- sb3(ios::in ios::out);
- stringbuf sb10(s0), sb11(s1, ios::in),
- sb12(s2, ios::out),
- sb13(s3, ios::in ios::out);
- ostream outs(&sb0);
- outs << "dynamic stringbuf 0";
- s3 = sb0.str();
- assert(s3 == "dynamic stringbuf 0");
- sb0.str(s0);
- assert(sb0.str() == "s0");
- outs.rdbuf(&sb2);
- outs << "dynamic stringbuf 2";
- assert(sb2.str() == "dynamic stringbuf 2");
- outs.rdbuf(&sb10);
- outs << "x";
- assert(sb10.str() == "x0");
- outs.rdbuf(&sb11);
- outs << "x";
- assert(!outs.good() && sb12.str() == "s1");
- outs.rdbuf(&sb12);
- outs << "x";
- assert(sb12.str() // "x");
- assert(sb12.pubseekoff(2, ios::beg).offset() == 2
- && sb12.str() == "x2");
- }
-
- void t2()
- { // test istringstream
- string s0("s0"), s1("s1"), s2("s2"), s3("s3");
- istringstream is0, is1(ios::in),
- is2(ios::out), is3(ios::in ios::out);
- istringstream is10(s0), is11(s1, ios::in),
- is12(s2, ios::out),
- is13(s3, ios::in ios::out);
- assert(is10.rdbuf()->str() == "s0")
- assert(is11.str() == "s1");
- is0.str("abc");
- assert(is0.str() == "abc");
- is0 >> s0;
- assert(s0 == "abc");
- }
-
-
- void t3()
- { // test ostringstream
- string s0("s0"), s1("s1"), s2("s2"), s3("s3");
- ostringstream os0, osl(ios::in),
- os2(ios::out), os3(ios::in ios::out);
- ostringstream os10(s0), os11(s1, ios::in),
- os12(s2, ios::out),
- os13(s3, ios::in ios::out);
- assert(os10.rdbuf()->str() == "");
- assert(os13.str() == "s3");
- os0.str("abc");
- assert(os0.str() == "");
- assert(os0.rdbuf()->pubseekoff(2, ios::beg).offset()
- == 2 && os0.str() == "ab");
- os0 << "Cde";
- assert(os0.str() == "abCde");
- }
-
- int main()
- { // test basic workings of stringstream definitions
- t1();
- t2();
- t3();
- cout << "SUCCESS testing <sstream>" << endl;
- return (0);
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Code Capsules
-
-
- The Standard C Library, Part 3
-
-
-
-
- Chuck Allison
-
-
- Chuck Allison is a regular columnist with CUJ and a Senior Software Engineer
- in the Information and Communication Systems Department of the Church of Jesus
- Christ of Latter Day Saints in Salt Lake City. He has a B.S. and M.S. in
- mathematics, has been programming since 1975, and has been teaching and
- developing in C since 1984. His current interest is object-oriented technology
- and education. He is a member of X3J16, the ANSI C++ Standards Committee.
- Chuck can be reached on the Internet at 72640.1507@compuserve. com.
-
-
- This month I conclude this series on the Standard C Library by exploring the
- headers in Group III (see Table 1 through Table 3).
-
-
- Group III: For the "Complete" C Programmer
-
-
-
-
- <float.h>
-
-
- Perhaps nothing varies so widely across different computer environments as
- floating-point number systems. Some platforms require special hardware to make
- native floating-point instructions available. Others provide floating-point
- capability through software, and MS-DOS compilers must support both
- implementations.
- A floating-point number system is a collection of numbers that can be
- expressed in scientific notation, with a fixed number of digits. To be
- precise, it is the finite set of numbers of the form ±0.d1d2..dp x be, where m
- £ e £ M. The parameters b, p, m, and M represent the radix, precision, minimum
- exponent, and maximum exponent, respectively. The header <float.h> provides
- manifest constants for these and other important floating-point parameters
- (see Table 4; also see the sidebar, "Floating-Point Number Systems"). As Table
- 4 illustrates, each implementation must support three potentially separate
- number systems, one each for float, double, and long double numbers.
-
-
- <math.h>
-
-
- Although C is not used widely in scientific programming, it provides a
- full-featured set of mathematical functions (see Table 6). Most of these
- functions have the standard names used in mathematics, so if you know what
- they are, you know how to use them. A few deserve special mention, however. As
- the program in Listing 4 illustrates, the fmod function computes the remainder
- of dividing its first argument by its second. The answer to fmod(1234.56,
- 90.1234) is 62.9558 because
- 1234.56 - (13 * 90.1234) == 62.9558
- The function modf stores the integer part of its first argument at the address
- given by its second argument, and returns the fractional part. frexp splits a
- floating-point number as given by its first argument into two parts, mantissa
- and base-2 exponent. In Listing 4, frexp computes a normalized fraction w and
- an integer p such that its first argument, y, is equivalent to
- w x 2p
- frexp's inverse, ldexp, returns a floating-point number given mantissa w and
- exponent p.
- The floor of a number is itself if that number is an integer; otherwise the
- floor of a number is the next adjacent integer to the "left" on the number
- line, so the following relationships are valid:
- floor(90.1234) == 90
- floor(-90.1234) == -91
- The ceiling of a number is the next integer to the right, so
- ceil(90.1234) == 91
- ceil(-90.1234) == -90
- The calculator program in Listing 5 illustrates a number of <math.h>
- functions.
-
-
- <errno.h>
-
-
- <errno.h> implements a simple error reporting facility. It defines a global
- integer, errno, to hold certain error codes that are generated by a number of
- library functions. The C Standard requires vendors to provide only two codes,
- EDOM and ERANGE (see Table 7). EDOM indicates a domain error, which usually
- means that you passed bad arguments to the function. For example, the sqrt
- function in <math. h> complains if you ask for the square root of a negative
- number. Math functions can set errno to ERANGE to indicate a range error,
- which mean that a calculation on valid arguments would result in an arithmetic
- overflow or underflow. Most of the mathematical functions in the standard
- library use errno to report such errors. As the calculator in Listing 5
- illustrates, you should set errno to zero before invoking any function that
- uses this facility, and then check it immediately after the function returns.
- The function perror prints its string argument followed by a colon, followed
- by a string representation of the last error recorded in errno. perror(s) is
- equivalent to the expression:
- printf("%s: %s\n",s,strerror(errno));
- strerror, defined in <string.h>, returns implementation-defined text that
- corresponds to errno.
- The following functions from other standard library headers also set errno
- upon failure: strod, strtol, strtoul, fgetpos, fsetpos, and signal. The C
- Standard allows an implementation to provide error codes of its own, beyond
- EDOM and ERANGE, and to use the errno facility with other functions, but such
- use is of course non-portable.
-
-
- <locale.h>
-
-
-
- A locale in Standard C is a collection of preferences for the processing and
- display of information that is sensitive to culture, language, or national
- origin, such as date and monetary formats. The Standard recognizes five
- categories of locale-specific information, named by macros in <locale.h> (see
- Table 8). Each of these categories can be set to a different locale (e.g.,
- "american", or "italian"). For want of a better term, I call the collection of
- settings for all five categories the locale profile.
- Standard C specifies two functions that deal with locales directly:
- struct lconv *localeconv(void);
- char *setlocale(int category, char *locale);
- The members of the lconv structure appear in Listing 6. localeconv returns a
- static lconv object containing current settings for LC_MONETARY and
- LC_NUMERIC, and setlocale changes the locale for the given category to that
- specified in its second argument. You can set all categories to the given
- locale by specifying a category of LC_ALL (see Listing 7). If locale is NULL,
- setlocale returns the current locale string for the category. All
- implementations must support the minimalist "C" locale, and a native locale
- named by the empty string (which may be the same as the "C" locale).
- Unfortunately, few U.S. vendors provide any additional locale support.
-
-
- <setjmp.h)
-
-
- When you encounter an exceptional condition deep within a set of nested
- function calls, you need a "super goto" that branches to a safe point higher
- up in the function call stack. That's what the setjmp/longjmp mechanism is
- for. You mark that safe return point with setjmp, and branch to it with
- longjmp. Here's the syntax:
- #include <setjmp.h>
-
- jmp_buf recover;
-
- main()
- {
-
- volatile int i = 0;
- for(;;)
- {
- if (setjmp(recover) != 0)
- {
- /* Recover from error in f() */
- }
-
- /* Get deeply nested... */
- }
- return 0;
- }
-
- . . .
-
- void f()
- {
- /* Do some risky stuff */
-
- if (<things go crazy>)
- longjmp(recover,1);
-
- /* else carry on */
- }
- A jmp_buf (jump buffer) is an array that holds the system information
- necessary to restore execution at the setjmp point. For obvious reasons, a
- jump buffer must typically be global. When you call setjmp, the system stores
- the calling environment parameters, such as the contents of the stack and
- instruction pointer registers, in recover. setjmp always returns a zero when
- called directly. A call to longjmp restores the calling environment, so
- execution continues back at the setjmp call point, with one difference: it
- appears as if setjmp has returned the second argument from the longjmp call,
- in this case a 1. (One small quirk: if you give longjmp a second argument of
- zero, it returns a 1 anyway. Zero is the only argument with this behavior.)
- Since a longjmp performs an alternate return from a function, it interrupts
- the normal flow of a program, so you should use it only to handle unusual
- conditions.
- When longjmp is called, the function containing the setjmp target must still
- be active (i.e., must not yet have returned to its own caller), otherwise the
- calling environment will no longer be valid. And of course, it is a bad idea
- to have more than one setjmp target with the same jmp_buf variable. Since
- calling environments typically involve registers, and since a compiler is free
- to store automatic variables in registers, you have no idea if an automatic
- object will have the correct value when you return from a longjmp. To get
- around this problem, you should declare automatics in any function containing
- a setjmp with the volatile qualifier, which guarantees that the inner workings
- of longjmp will leave them undisturbed. For a more detailed example of the
- setjmp/longjmp mechanism, see Listing 9 in "Code Capsules, File Processing,
- Part 2," CUJ, June 1993.
-
-
- <signal.h>
-
-
- A signal occurs when an unusual event interrupts the normal execution of a
- program, such as a divide-by-zero error or when the user presses an attention
- key, such as Control-C or DEL. The header <signal. h> defines six "standard
- signals," shown in Table 9. These signals originated on the PDP architecture
- under UNIX, so some of them may not apply to your environment. An
- implementation may also define other signals, or may ignore signals
- altogether, so signal handling is inherently non-portable.
- Signals come in two flavors: synchronous and asynchronous. A synchronous
- signal is one that your program raises, such as dividing by zero, overflowing
- during a floating-point operation, or issuing a call to the abort function.
- You can also raise a signal explicitly with a call to raise, for example:
- raise(SIGABRT);
- An asynchronous signal occurs as a result of events that your program can't
- foresee, such as a user pressing the attention key.
- The default response to most signals is usually to abort the program, but you
- can either arrange for signals to be ignored or provide your own custom signal
- handlers. The following statement from Listing 8
- signal(SIGINT,SIG_IGN);
- tells the environment to ignore any keyboard interrupt requests (Control-C on
- my machine). The keyboard input process still echoes the ^C token on the
- display, but it does not pass control to the default signal-handling logic
- that terminates the program. The statement
- signal(SIGINT,SIG_DFL)
- restores the default behavior, so you can halt the program from the keyboard.
-
- For more on signal handling, see Listing 11 - Listing 13 in the Code Capsule
- "Control Structures," in the June 1994 issue of CUJ.
-
-
- <stdarg.h>
-
-
- This header provides a facility for defining functions with variable-length
- argument lists, like printf's (see Table 10). printf's function prototype in
- your compiler's stdio.h should look something like
- int printf(const char *, ...);
- The ellipsis tells the compiler to allow zero or more arguments of any type to
- follow the first argument in a call to printf. For printf to behave correctly,
- the arguments that follow the format string must match the types of the
- corresponding edit descriptors in the format string. If there are fewer
- arguments than the format string expects, the result is undefined. If there
- are more arguments, they are ignored. The bottom line is that when you use the
- ellipsis in a function prototype you are telling the compiler not to
- type-check your optional arguments because you think you know what you're
- doing -- so be sure that you do.
- The program in Listing 9 shows how to use the va_list mechanism to find the
- largest integer in a variable-length argument list. For more on <stdarg.h>,
- see the Code Capsule, "Variable-length Argument Lists" in the Feb. 1994 issue
- of CUJ.
-
-
- Conclusion
-
-
- In this three-part series I have attempted to convey the overall flavor of the
- Standard C library, especially in the functionality it provides. It is foolish
- to waste time reinventing this functionality. Although I have classified the
- library into three groups of decreasing priority, my priorities may not match
- yours. If you make your living programming in C or C++, you will do well to
- master the entire library. Enough said.
- Floating-point Number Systems
- The computer's difficulty in manipulating real numbers has long been a source
- of frustration and confusion for students and practitioners alike. The program
- in Listing 1 shows how a simple sequence of sums can go awry. It calculates
- the expression ex by the formula:
- Click Here for Equation
- and compares the result to the "correct" answer given by the exp function in
- the standard library. This works correctly for positive arguments, but the
- result for negative arguments isn't even close! The problem, of course, is
- that computers, with their finite capabilities, can only represent a minuscule
- subset of the set of real numbers. A good chunk of numerical analysis deals
- with roundoff error, the quirks of finite-precision arithmetic.
- In days gone by, many computers used a fixed-point number system, which uses
- numbers derived from a given radix (b), precision (p), and fraction size (f).
- For example, the values b=10, p=4, and f=1 define the following set of 19,999
- numbers:
- F = {-999.9, - 999.8,...,999.8, 999.9}
- Since these numbers are evenly spaced, the maximum absolute error in
- representing any real number x in this system is bounded by 0.05. In other
- words,
- x-fix(x) £ 0.05
- The set of machine integers form a fixed-point system with f=0, and a maximum
- absolute error of 0.5 for systems that round, and no greater than 1.0 for
- systems that truncate.
- Absolute error is not generally useful in mathematical computations, however.
- As is often the case, you are more often interested in percentages, or how one
- number differs in relation to another. You compute the relative error of y
- with respect to x by the following formula:
- Click Here for Equation
- Consider how the numbers 865.54 and 0.86554 are represented in F:
- fix(865.54) = 865.5
- fix(.86554) = 0.9
- Because the number of decimals is fixed, the second is a much poorer fit than
- the first, which the relative errors illustrate:
- Click Here for Equation
- The second is 1,000 times worse than the first!
- Nowadays computers provide floating-point number systems, which represent
- numbers in the form
- ±0.d1d2...dp X be
- where
- m£e£M, 0£di<b
- This is like the scientific notation that you learned in school, except that
- in this case the base (b) can be other than 10, and there is an upper limit on
- the precision (p). Most floating-point systems use a normalized
- representation, which means that d1 cannot be zero. These number systems are
- called floating-point for the obvious reason -- the radix point "floats" and
- the exponent (e) adjusts accordingly to preserve the correct value.
- Consider a floating-point system G defined by b=10, p=4, m=-2, and M=3. The
- numbers from the fixed-point example above are represented as:
- fl(8.65.54)+0.8655 x 103
- fl(865.554)=0.8655 x 100
- Now look what happens when calculating the relative errors of the two
- representations:
- Click Here for Equation
- Click Here for Equation
- It can be shown that the relative error of representing any real number within
- the range of a floating-point system is no greater than b1-p, which is 0.001
- in G.
- Floating-point numbers are not evenly spaced. In G, for example, the next
- number greater than 1 is 1.001, a spacing of 0.001, but the next number after
- 10 is 10.01, which is 0.01 to the right. In general, the spacing among all
- numbers between powers of b, say between be and be+1, is be+1-p. A trivial
- combinatorial analysis shows that each interval [be, be+1] has (b--1) bp-1
- floating-point numbers, and the total number of representable floating-point
- numbers in a system is 2(M--m+1)(b--1)b, pp-1 (so increasing p increases the
- density of the number system). And as shown above, although smaller numbers
- are closer together and larger numbers are farther apart, the relative spacing
- between consecutive floating-point numbers is essentially the same throughout
- the system. In fact, the relative spacing between two adjacent representable
- numbers within an interval [be, be+1] is b1-p, and between be+1 and its
- immediate predecessor is b-p.
- The quantity b1-p, which also happens to be the spacing between 1.0 and its
- immediate successor, is called the machine epsilon (e), and is a good measure
- of the granularity of a floating number system. The smallest floating-point
- magnitude other than zero is of course b, usually denoted as s, and the
- largest magnitude, l, is bM(1--b-p).
- The Standard C header <float.h> provides manifest constants for all of these
- parameters as follows:
- b == FLT_RADIX
- p == DBL_MANT_DIG
- m == DBL_MIN_EXP
- M == DBL_MAX_EXP
- e == DBL_EPSILON
- s == DBL_MIN
- l == DBL_MAX
- <float.h> also provides the float and long double equivalents of the last five
- parameters, representing the three (not necessarily distinct) floating-point
- number systems in standard C (see Table 4).
- The program in Listing 2 calculates a root of x2+x+1 in the interval [--1, 1]
- by the method of bisection. The algorithm halves the interval (which must
- contain a sign change for the function F) until the relative spacing between
- the endpoints is less than or equal to machine epsilon. Such a loop may never
- terminate if you expect greater precision than this.
- Should you find yourself in an environment that does not provide the
- parameters in <float.h>, you can compute b, p, and e directly. To see how this
- is possible, remember that the spacing between consecutive floating-point
- numbers increases with the magnitude of the numbers. Eventually there is a
- point where the spacing between adjacent numbers is greater than 1. (In fact,
- the first interval where this holds true is [bp, bp+1], because the integers
- therein require p+1 digits in their representation.) The positive integers
- precisely representable in a floating-point system are these:
- 1,2,...,bp-1,bp,bp+b,bp+2b,..., bp+1,bp+1+b2,...
-
- To find the radix, the program in Listing 3 keeps doubling a until it reaches
- a point where the spacing exceeds 1. It then finds the next larger
- floating-point number and subtracts a to get b. The program then computes the
- smallest power of b that will produce numbers whose adjacent spacing exceeds 1
- that power is the precision p. To find e, the program finds the nearest
- representable neighbor to the right of 1, and subtracts 1.
- Table 1 Standard C Headers: Group I (required knowledge for every C
- programmer)
- <ctype.h> Character Handling
- <stdio.h> Input/Output
- <stdlib.h> Miscellaneous Utilities
- <string.h> Text Processing
- Table 2 Standard C Headers: Group 11 (tools for the professional)
- <assert.h> Assertion Support for Defensive Programming
- <limits.h> System Parameters for Integer Arithmetic
- <stddef.h> Universal Types & Constant
- <time.h> Time Processing
- Table 3 Standard C Headers: Group III (power at your fingertips when you need
- it)
- <errno.h> Error Detection
- <float.h> System Parameters for Real Arithmetic
- <locale.h> Cultural Adaptation
- <math.h> Mathematical Functions
- <setjmp.h> Non-local Branching
- <signal.h> Interrupt Handling (sort of)
- <stdarg.h> Variable-length Argument Lists
- Table 4 Definitions in <float.h>
- Parameter Meaning "Minimum"
- Value
- --------------------------------------------------------------
- FLT_RADIX exponent base 2
- FLT_ROUNDS rounding mode (see Table 5)
- FLT_MANT_DIG precision for float
- DBL_MANT_DIG precision for double
- LDBL_MANT_DIG precision for long double
- FLT_DIG base-10 precision for float 6
- DBL_DIG same for double 10
- LDBL_DIG same for long double 10
- FLT_MIN_EXP min. exponent for float
- DBL_MIN_EXP same for double
- LDBL_MIN_EXP same for long double
- FLT_MIN_10_EXP min. base-10 float exponent -37
- DBL_MIN_10_EXP same for double -37
- LDBL_MIN_10_EXP same for long double -37
- FLT_MIN smallest float 1E-37
- DBL_MIN smallest double 1E-37
- LDBL_MIN smallest long double 1E-37
- FLT_MAX_EXP max. exponent for float
- DBL_MAX_EXP same for double
- LDBL_MAX_EXP same for long double
- FLT_MAX_10_EXP max. base-10 float exponent +37
- DBL_MAX_10_EXP same for double +37
- LDBL_MAX_10_EXP same for long double +37
- FLT_MAX largest float 1E+37
- DBL_MAX largest double 1E+37
- LDBL_MAX largest long double 1E+37
- FLT_EPSILON machine epsilon for float 1E-5
- DBL_EPSILON same for double 1E-9
- LDBL_EPSILON same for long double 1E-9
- Table 5 Values for FLT_Rounds
- -1 indeterminable
- 0 toward zero
- 1 to nearest
- 2 toward positive infinity
- 3 toward negative infinity
- Table 6 Functions defined in <math.h>
-
- acos arc-osine
- asin arc-sine
- atan arc-tangent (principal value)
- atan2 arc-tangent (full circle)
- ceil ceiling
- cos cosine
- cosh hyperbolic cosine
- exp power of e
- fabs absolute value
- floor floor (greatest-integer-in
- function)
- fmod modulus (remainder)
- frexp normalized fraction/
- exponent parts
- ldexp inverse of frexp
- log natural logarithm
- log10 logarithm base 10
- modf integer/fractional parts
- pow raise a number to a power
- sin sine
- sinh hyperbolic sine
- sqrt square root
- tan tangent
- tanh hyperbolic tangent
- Table 7 Definitions in <errno.h>
- errno Global integer to report
- certain errors
- EDOM Domain error code
- ERANGE Range error code
- Table 8 Locale Categories
- Category Functionality
- ----------------------------------------------------------------------
- LC_COLLATE Adapts strcoll, strxfrm, wcscoll, and wcsxfrm to the
- language/culture of the locale.
- LC_CTYPE Adapts the isxxx/iswxxx functions in ctype.h to the
- character set associated with the locale, and affects
- multibyte/wide character mapping.
- LC_MONETARY Sets parameters pertaining to the display of monetary
- values such as decimal point, grouping (e.g., thousands),
- group separator, etc. This category is purely advisory and
- affects no standard library functions.
- LC_NUMERIC Like LC_MONETARY, but for non-monetary values (e.g., the
- decimal point may be different than for non-monetary
- values).
- LC_TIME Adapts strftime and wcsftime to cultural specifications.
- Table 9 Definitions in <signal.h>
- Signal Macros
- -----------------------------------------------
- SIGABRT abnormal termination
- (raised by abort)
- SIGFPE computational exception
- (e.g., overflow)
- SIGILL invalid function image
- (e.g., illegal instruction)
- SIGINT interactive attention (e.g.,
- Control-C)
- SIGSEGV attempt to access protected
- memory
- SIGTERM termination request
-
-
- Functions
- -----------------------------------------------
- raise generates a signal from
- within a program
- signal registers a function as a
- signal-handler
- Table 10 Definitions in <stdarg.h>
- Type
- ---------------------------------------------
- va_list A variable-length argument list
-
- Macros
- -----------------------------------------------
- va_start Initializes a va_list
- va_arg Gets next arg in a va_list
- va_end Closes a va_list
-
- Listing 1 Shows roundoff error in computing powers of e
- #include <stdio.h>
- #include <math.h>
-
- double e(double x);
-
- main()
- {
- printf("e(55.5) == %g, exp(55.5) == %g\n",
- e(55.5), exp(55.5));
- printf("e(-55.5) == %g, exp(-55.5) == %g\n",
- e(-55.5), exp(-55.5));
- printf("1/e(55.5) == %g\n",1.0 / e(55.5));
- return 0;
- }
-
- double e(double x)
- {
- double sum1 = 1.0;
- double sum2 = 1.0 + x;
- double term = x;
- int i = 1;
-
- /* Calculate exp(x) via Taylor Series */
- while (sum1 != sum2)
- {
- sum1 = sum2;
- term = term * x / ++i;
- sum2 += term;
- }
- return sum2;
- }
-
- /* Output:
- e(55.5) == 1.26866e+24, exp(55.5) == 1.26866e+24
- e(-55.5) == -6.76351e+06, exp(-55.5) == 7.88236e-25
- 1/e(55.5) == 7.88236e-25
- */
-
- /* End of File */
-
-
-
- Listing 2 Illustrates use of machine epsilon in a root-finding algorithm
- /* root.c:
- *
- * To use a different precision, change ftype
- * and the suffix of the floating-point constants
- */
-
- #include <stdio.h>
- #include <float.h>
- #include <math.h>
- #include <assert.h>
-
- #define sign(x) ((x < 0.0) ? -1 : (x > 0.0) ? 1 : 0)
-
- #define PREC DBL_DIG
- #define EPS DBL_EPSILON
-
- typedef double ftype;
-
- ftype root(ftype a, ftype b, ftype (*f)(ftype))
- {
-
- ftype fofa = f(a);
- ftype fofb = f(b);
- assert(a < b);
- assert(fofa * fofb < 0.0);
-
- /* Close-in on root via bisection */
- while (fabs(b - a) > EPS*fabs(a))
- {
- ftype x = a + (b-a)/2.0;
- ftype fofx = f(x);
- if (x <= a x >= b fofx == 0.0)
- return x;
- if (sign(fofx) == sign(fofa))
- {
- a = x;
- fofa = fofx;
- }
- else
- {
- b = x;
- fofb = fofx;
- }
- }
- return a;
- }
-
- main()
- {
- extern ftype f(ftype);
- printf("root == %.*f\n",PREC,root(-1.0,1.0,f));
- return 0;
- }
-
- ftype f(ftype x)
- {
- return x*x + x - 1.0;
-
- }
-
- /* Output:
- root == 0.618033988749895
- */
- /* End of File */
-
-
- Listing 3 Computes Machine Floating-point Parameters
- #include <stdio.h>
- #include <math.h>
- #include <float.h>
-
- main()
- {
- int beta, p;
- double a, b, eps, epsp1, sigma, nums;
-
- /* Discover radix */
- a = 1.0;
- do
- {
- a = 2.0 * a;
- b = a + 1.0;
- } while ((b - a) == 1.0);
-
- b = 1.0;
- do
- b = 2.0 * b;
- while ((a + b) == a);
- beta = (int) ((a + b) - a);
- printf("radix:\n");
- printf("\talgorithm: %d\n",beta);
- printf("\tprovided: %d\n",FLT_RADIX);
-
- /* Compute precision in bits */
- p = 0;
- a = 1.0;
- do
- {
- ++p;
- a *= (double) beta;
- b = a + 1.0;
- } while ((b - a) == 1.0);
- printf("precision:\n");
- printf("\talgorithm: %d\n",p);
- printf("\tprovided: %d\n",DBL_MANT_DIG);
-
- /* Compute machine epsilon */
- eps = 1.0;
- do
- {
- eps = 0.5 * eps;
- epsp1 = eps + 1.0;
- } while (epsp1 > 1.0);
- epsp1 = 2.0 * eps + 1.0;
- eps = epsp1 - 1.0;
- printf("machine epsilon:\n");
- printf("\talgorithm: %g\n",eps);
-
- printf("\tformula: %g\n",pow(FLT_RADIX,1.0-DBL_MANT_DIG));
- printf("\tprovided: %g\n",DBL_EPSILON);
-
- /* Compute smallest normalized magnitude */
- printf("smallest non-zero magnitude:\n");
- printf("\tformula: %g\n",pow(FLT_RADIX,DBL_MIN_EXP-1));
- printf("\tprovided: %g\n",DBL_MIN);
-
- /* Compute larget normalized magnitude */
- printf("largest non-zero magnitude:\n");
- printf("\tformula: %g\n",
- pow(FLT_RADIX,DBL_MAX_EXP-1) * FLT_RADIX *
- (1.0 - pow(FLT_RADIX,-DBL_MANT_DIG)));
- printf("\tprovided: %g\n",DBL_MAX);
-
- printf("smallest exponent: %d\n",DBL_MIN_EXP);
- printf("largest exponent: %d\n",DBL_MAX_EXP);
-
- nums = 2 * (FLT_RADIX - 1)
- * pow(FLT_RADIX,DBL_MANT_DIG-1)
- * (DBL_MAX_EXP - DBL_MIN_EXP + 1);
- printf("This system has %g numbers\n",nums);
- return 0;
- }
-
- /* Output (Borland C++):
- radix:
- algorithm: 2
- provided: 2
- precision:
- algorithm: 53
- provided: 53
- machine epsilon:
- algorithm: 2.22045e-16
- formula: 2.22045e-16
- provided: 2.22045e-16
- smallest non-zero magnitude:
- formula: 2.22507e-308
- provided: 2.22507e-308
- largest non-zero magnitude:
- formula: 1.79769e+308
- provided: 1.79769e+308
- smallest exponent: -1021
- largest exponent: 1024
- This system has 1.84287e+19 numbers
- */
- /* End of File */
-
-
- Listing 4 Illustrates several <math.h> functions
- #include <stdio.h>
- #include <math.h>
-
- main()
- {
- double x = 1234.56, y = 90.1234, z, w;
- int p;
-
- printf("x == %g, y == %g\n",x,y);
-
- printf("fmod(x,y) == %g\n",fmod(x,y));
- printf("floor(y) == %g\n",floor(y));
- printf("ceil(y) == %g\n",ceil(y));
- w = modf(y,&z);
- printf("after modf(y,&z): w == %g, z == %g\n",w,z);
- w = frexp(y,&p);
- printf("after frexp(y,&p): w == %g, p == %d\n",w,p);
- printf("ldexp(w,p) == %g\n",ldexp(w,p));
- return 0;
- }
-
- /* Output:
- x == 1234.56, y == 90.1234
- fmod(x,y) == 62.9558
- floor(y) == 90
- ceil(y) == 91
- after modf(y,&z): w == 0.1234, z == 90
- after frexp(y,&p): w == 0.704089, p == 7
- ldexp(w,p) == 90.1234
- */
- /* End of File */
-
-
- Listing 5 A simple calculator that illustrates some <math.h> functions
- /* calc.c: Lame-brain Calculator-
- *
- * For simplicity in parsing, this program
- * reads lines of the form:
- *
- * value operation
- *
- * where the value is optional in some cases.
- * For example, the following script computes
- * the integer part of sqrt(1 + 3.4*3.4):
- *
- * 3.4 =
- * 3.4 *
- * 1 +
- * sqrt
- * floor
- */
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <math.h>
- #include <string.h>
- #include <ctype.h>
- #include <errno.h>
-
- #define LINSIZ 40
-
- char *getline(char *);
-
- main()
- {
- double reg = 0.0;
- char line[LINSIZ];
- while (getline(line) != NULL)
- {
-
- char *op;
- double val;
-
- /* Parse command string */
- val = strtod(line,&op);
- while (isspace(*op))
- ++op;
- strupr(op);
-
- /* Perform operation */
- errno = 0;
- if (*op == '+')
- reg += val;
- else if (*op == '-')
- reg -= val;
- else if (*op == '*')
- reg *= val;
- else if (*op == '/')
- {
- if (val != 0)
- reg /= val;
- else
- {
- puts("ERROR>>> invalid divisor");
- continue;
- }
- }
- else if (*op == '=')
- reg = val;
- else if (*op == '^')
- {
- if (val == 0.0)
- reg = 1.0;
- else if (val == 0.5)
- reg = sqrt(reg);
- else
- reg = pow(reg,val);
- }
- else if (strncmp(op,"NEGATE",1) == 0)
- reg = -reg;
- else if (strncmp(op,"MOD",1) == 0)
- {
- if (val == 0.0)
- {
- puts("ERROR>>> invalid modulus");
- continue;
- }
- else
- reg = fmod(reg,val);
- }
- else if (strncmp(op,"CEIL",1) == 0)
- reg = ceil(reg);
- else if (strncmp(op,"FLOOR",1) == 0)
- reg = floor(reg);
- else if (strncmp(op,"ROUND",1) == 0)
- reg = (reg < 0.0) ? ceil(reg - 0.5)
- : floor(reg + 0.5);
- else if (strncmp(op,"SQRT",1) == 0)
- reg = sqrt(reg);
-
- else if (strncmp(op,"QUIT",1) == 0)
- exit(0);
- else if (*op != '\0')
- {
- puts("ERROR>>> invalid operation");
- continue;
- }
-
- if (errno)
- perror("ERROR>>>");
- else
- printf("\t%s => %g\n",line,reg);
- }
- return 0;
- }
-
- char *getline(char *buf)
- {
- fputs ("Calc> ",stdout);
- fflush(stdout);
- return gets(buf);
- }
-
- /* Output:
- Calc> 3.4 =
- 3.4 = => 3.4
- Calc> 3.4 *
- 3.4 * => 11.56
- Calc> 1 +
- 1+ => 12.56
- Calc> sqrt
- SQRT => 3.54401
- Calc> floor
- FLOOR => 3
- Calc> q
- */
-
- /* End of file */
-
-
- Listing 6 The members of struct lconv
- struct lconv
- {
- 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 frac_digits;
- char p_cs_precedes;
- char p_sep_by_space;
- char n_cs_precedes;
- char n_sep_by_space;
-
- char p_sign_posn;
- char n_sign_posn;
- };
-
-
- Listing 7 Shows the effect of locale settings on the decimal point character
- and time formatting
- /* tlocale.c: Illustrates locales-
- *
- * Compiled in Visual C++ under Windows NT 3.5
- */
-
- #include <locale.h>
- #include <stdio.h>
- #include <time.h>
-
- void print_stuff(void);
-
- main()
- {
- /* First in the C locale */
- puts("In the C locale:");
- print_stuff();
-
- /* Now try German */
- puts("\nIn the German locale:");
- setlocale(LC_ALL,"german");
- print_stuff();
-
- /* Now try American */
- puts("\nIn the American locale:");
- setlocale(LC_ALL,"american");
- print_stuff();
-
- /* Now try Italian */
- puts("\nIn the Italian locale:");
- setlocale(LC_ALL,"italian");
- print_stuff();
- return 0;
- }
-
- void print_stuff(void)
- {
- char text[81];
- time_t timer = time(NULL);
- struct lconv *p = localeconv();
-
- printf("decimal pt. == %s\n",
- p->decimal_point);
- printf("currency symbol == %s\n",
- p->int_curr_symbol);
- printf("%.2f\n",l.2);
- strftime(text,sizeof text,"%A, %B, %d,
- %Y (%x)\n", localtime(&timer));
- puts(text);
- }
-
- In the C locale:
- decimal pt. == ,
- currency symbol ==
-
- 1.20
- Tuesday, January 03, 1995 (01/03/95)
-
-
- In the German locale:
- decimal pt. == .
- currency symbol == DEM
- 1,20
- Dienstag, Januar 03, 1995 (03.01.95)
-
-
- In the American locale:
- decimal pt. == .
- currency symbol == USD
- 1.20
- Tuesday, January 03, 1995 (01/03/95)
-
-
- In the Italian locale:
- decimal pt. == ,
- currency symbol == ITL
- 1,20
- marted, gennaio 03, 1995 (03/01/95)
- /* End of File */
-
-
- Listing 8 Shows how to register a signal
- /* ignore.c: Ignores interactive attention
- key as */
- #include <stdio.h>
- #include <signal.h>
-
- main()
- {
- char buf[BUFSIZ];
- int i;
-
- /* Ignore keyboard interruptions */
- signal(SIGINT,SIG_IGN);
-
- while (gets(buf))
- puts(buf);
-
- /* Restore default attention key handling
- so the user can abort form the keyboard */
- signal(SIGINT,SIG_DFL);
- for (i = 1; ; ++i)
- printf("%d%c",i,(i%15 ? ' ' : '\n'));
- }
-
- /* Sample Execution
- c:>ignore
- hello
- hello
- ^C
- there
- there
- ^C
- ^Z
-
- 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
- 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
- 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
- 46 45 48 49 50 51 52 53 54 55 56 57 58 59 60
- 61 62 63 64 65 66 67 ^C
- */
- /* End of file*/
-
-
- Listing 9 Uses the <stdarg.h> macros to search a variable-length list of
- integers
- /* max.c */
- #include <stdio.h>
- #include <stdarg.h>
-
- int maxn(size_t count, ...)
- {
- int n, big;
- va_list numbers;
-
- va_start(numbers,count);
-
- big = va_arg(numbers,int);
- while (count--)
- {
- n = va_arg(numbers,int);
- if (n > big)
- big = n;
- }
-
- va_end(numbers);
- return big;
- }
-
- main()
- {
- printf("max = %d\n",maxn(3,1,3,2));
- return 0;
- }
-
- /* Output:
- max = 3
- */
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Stepping Up To C++
-
-
- More Minor Enhancements as of CD Registration
-
-
-
-
- Dan Saks
-
-
- Dan Saks is the president of Saks & Associates, which offers consulting and
- training in C++ and C. He is secretary of the ANSI and ISO C++ committees. Dan
- is coauthor of C++ Programming Guidelines, and codeveloper of the Plum Hall
- Validation Suite for C++ (both with Thomas Plum). You can reach him at 393
- Leander Dr., Springfield OH, 45504-4906, by phone at (513)324-3601, or
- electronically at dsaks@wittenberg.edu.
-
-
- For the past two months I've been listing ways that the programming language
- described by the C++ draft standard (as of Fall, 1994) differs from the
- language described in the ARM [1]. Two months ago, I described the major
- extensions:
- Templates
- Exception handling
- Run-time type information (including dynamic_cast)
- Namespaces
- (See "Stepping Up to C++: C++ at CD Registration," CUJ, January 1995.) Last
- month, I started describing the minor enhancements, and managed to cover the
- following:
- New keywords and digraphs for C++ as alternate ISO646-compliant spellings for
- existing tokens
- Operator overloading on enumerations
- operator new[] and operator delete[]
- Relaxed restrictions on the return type of virtual functions
- wchar_t as a keyword representing a distinct type
- A built-in Boolean type
- Declarations in conditional expressions
- This month, I continue by describing a few more of the remaining minor
- enhancements.
-
-
- New cast notation
-
-
- The extensions to support RTTI (run-time type information) included a new type
- conversion operator called dynamic_cast. An expression such as
- dynamic_cast<T *>(p)
- yields the result of casting (converting) pointer p to type T *. If p points
- to a T object, the result is p. If p points to an object of a type D derived
- from T, then the result is a pointer to the unique T sub-object of the D
- object addressed by p. (In fact, these conversions do not even require a
- cast.) Otherwise, p must point to an object of a polymorphic type (a type with
- at least one virtual function), in which case the resulting program performs a
- run-time check.
- The run-time check is this: If p points to a base class sub-object of a T
- object, the result is a pointer to that T object; otherwise, it yields a null
- pointer. (This description of the run-time check is over-simplified, but it
- covers the most common and useful cases, namely public inheritance from a
- single direct base class.) Listing 1 shows an example of this sort of
- conversion, commonly called a downcast.
- dynamic_cast can also perform reference conversions. An expression such as
- dynamic_cast<T &>(r)
- converts reference r to type T &. The rules for dynamic_cast applied to
- references parallel the rules when applied to pointers, except that a
- reference conversion throws an exception, rather than return null, when it
- fails.
- The dynamic_cast notation is obviously, and intentionally, different from the
- "old-style" cast notation, (T)e, which converts expression e to type T. The
- proponents of RTTI did not want to confuse this new functionality with
- existing conversions by using the same syntax.
- In the course of distinguishing dynamic_cast from the other casts, the C++
- standards committees generally agreed with Bjarne Stroustrup that the
- old-style cast lumps too many different kinds of conversions under a single
- blanket notation [2]. Among other things, an old-style cast can do the
- following:
- narrow or widen an arithmetic value,
- discard a const or volatile attribute from an expression,
- perform implicit address arithmetic while converting B * to D * (where D is
- derived from B), or
- reinterpret the value of an expression of one type as a value with a
- completely different type.
- Stroustrup also argued that the old-style cast notation is too indistinct.
- Old-style casts are just one of many uses for parentheses in C and C++. These
- casts are hard for humans to spot in source code, and hard to find using tools
- like grep.
- The standards committees accepted Stroustrup's proposal to add three new cast
- operators in the mold of dynamic_cast. These casts do not add any new
- functionality to C++. They simply classify the functionality of old-style
- casts into three distinct categories:
- static_cast<T>(e) is for the better-behaved conversions, such as from one
- arithmetic type to a narrower wider arithmetic type or to an enumeration, from
- a pointer-to-base (reference- to-base) to pointer-to-derived
- (reference-to-derived), or from any type to void. static_cast can add, but not
- remove cv- qualifiers (const and volatile).
- reinterpret_cast<T>(e) is for the poorly-behaved conversions (those with
- implementation-dependent behavior), such as converting from an integral type
- to a pointer or vice versa, to or from a pointer to an incomplete type, or
- from one pointer-to-function type to another.
- const_cast<T>(e) is for converting a type with cv-qualifiers to a type with
- fewer cv-qualifiers.
- For compatibility with C and existing C++ code, the committees did not remove,
- nor even deprecate, the old-style casts. (A deprecated feature is one that has
- fallen into disfavor and may disappear altogether from some future version of
- the standard.) The new-style casts are just a more explicit alternative
- notation.
- For example, the implementation of the Standard C function bsearch typically
- involves converting a parameter from const void * to const char *, and
- returning it as void *. Plauger's implementation [3] uses an old style cast in
- the return statement
- return ((void *)q);
- to convert q from const char *to void *. The current C++ view is that this
- cast actually performs two distinct conversions:
- 1. It converts the type from char *to void *.
- 2. It removes ("casts-away") a const qualifier. It's not apparent from a
- casual reading of that return statement that the (void *) cast removes a const
- qualifier.
-
- Plauger wrote his library in C, so he had no choice about the cast notation,
- but a C++ programmer can now write the conversion more explicitly using the
- new style-casts:
- return static_cast<void *>(const_cast<char *>(q));
- Writing the return as
- return static_cast<void *>(q);
- is an error, because static_cast cannot cast-away a const qualifier. Writing
- return const_cast<void *>(q);
- is also an error, because a const_cast can only change the cv-qualification,
- not the type.
- For much more about the new-style casts, see [2].
-
-
- Qualified Names in Elaborated-Type-Specifiers
-
-
- C places struct, union, and enum tags in a namespace separate from ordinary
- identifiers (those that designate functions, objects, typedefs, and
- enumeration constants). For example, the declaration
- struct X { ... };
- enters X as a struct name in the tag namespace. Because X is a tag, not a
- type, you cannot write declarations such as
- X *p;
- You must write the declaration as
- struct X *p;
- Some C programmers take advantage of this distinction between tags and
- ordinary identifiers, and write declarations like
- struct X { ... } X;
- which declares a struct X, and then an object X of type struct X, all in one
- declaration. Other C programmers, including yours truly, deem this bad
- practice, preferring to think of tags as type names.
- I always equate each tag name to a type name using a typedef. A declaration of
- the form
- typedef struct X { ... } X;
- often does the trick. Unfortunately, this doesn't introduce the typedef name
- until after the body of the struct. I prefer writing such declarations in the
- form:
- typedef struct X X;
- struct X { ... };
- C++ treats tags differently from C. In C++, a class name is still a tag name,
- but it is also a type name. In fact, C++ treats all tags (for classes,
- structs, unions, and enums) as types. Thus, given
- struct X { ... };
- C++ lets you use X as an ordinary identifier designating a type, as in
- X *p;
- For compatibility with C, C++ still accepts
- struct X *p;
- In C++, the combination of one of the keywords class, struct, union or enum,
- followed by a tag, is called an elaborated-type-specifier.
- Again in the name of compatibility with C, C++ tolerates declarations such as
- struct X { ... } X;
- In this case, the object name X hides the type name X, so that subsequent uses
- of the unelaborated name X refer to the object. However, you can refer to the
- type name by using its elaborated form struct X, or by using X in a qualified
- name such as X::m (where m is a member of X).
- Now, with this background in mind, consider the declaration in Listing 2.
- Here, B appears as two different members of A: as a type and as a data member.
- The data member hides the type, so that outside A, A::B refers to the data
- member, not the nested type. You can refer to B as a type by using it in a
- qualified name like A::B::n, because C++ looks up a name followed by :: as if
- that name were a type. But how do you refer to B itself as a type?
- For example, given the declaration in Listing 2, a declaration such as
- A::B *pb;
- is an error, because the data member name B hides the type name B in the scope
- of A. In fact, the ARM provide no notation for referring to A::B as a member
- type.
- The committees rectified this minor problem with a minor grammatical extension
- to allow a class-key (the keyword class, struct, or union) in an
- elaborated-type-specifier. For example, you can now write
- struct A::B *pb;
- to declare pb as a pointer to an object of type A::B. Notice that the keyword
- struct elaborates B, not A. It's already clear that A designates a type in
- this context because A is followed by ::.
- This extension also permits the keyword enum in an elaborated-type-specifier.
- For example,
- enum A::E *pe;
- declares pe as a pointer to an object of enumeration type A::E.
-
-
- Expressions of the form a.::B::c and p->::B::c
-
-
- The committees straggled for several years to correct problems in the ARM's
- specification of scope and name lookup rules. The ARM's rules are incomplete,
- and at various times imprecise and contradictory. (See "Stepping Up to C++:
- Looking Up Names," CUJ, August 1993 and "Stepping Up to C++: Rewriting and
- Reconsidering," CUJ, September 1993.)
- One of the last problems in this area was to pin down the precise meaning of
- expressions of the form x.B::c (or p->B::c). B might be as simple as an
- identifier or as complicated as a template expression. How does a C++ compiler
- evaluate (look up) B in such expressions? The answer to this question covers
- more complicated expressions such as x.B::C::d, because once the compiler
- knows what B denotes, it can easily resolve C and d.
- After considering different rules for looking up B in x.B::c, the committee
- narrowed the choice to two possibilities:
- 1. Look up B in the context where it appears (known as the "golf" rule because
- you "play it where it lies")
- 2. Look up B as if it were in the body of a member function of x. That is,
- look in x's class (and its bases), then look in x's lexically enclosing class
- (and its bases), and so on out to global scope.
- There are arguments in favor of both rules. Suppose you have a class with an
- inconveniently long name, such as class VeryLongName shown in Listing 3. You
- can write terser code by defining a shorter alias for the class name, such as:
- typedef VeryLongName VLN;
-
- When a C++ compiler evaluates
- ap->VLN::f();
- in function g of Listing 3, it won't find VLN if it only looks in the scope of
- A (which is the scope of *ap). This is especially true if you rearrange lines
- // 1 and // 2, as shown in Listing 4. This aliasing technique only works if
- C++ applies the golf rule (rule 1).
- On the other hand, rule (2) supports different programming techniques that can
- also be useful. Sometimes, the author of a derived class may wish to provide
- users access to hidden names in the base class, without requiring that users
- know the name of the base class. This gives class designers a little more
- flexibility to change the class hierarchy without forcing the users to also
- change their code. The example in Listing 5 illustrates the problem and a
- solution.
- In Listing 5, D::f() hides inherited member B::f(). Normally, the function
- call in
- void g(D *pd)
- {
- pd->f();
- ...
- }
- applies D::f() to the D object addressed by pd. Function g could call B::f()
- with a call such as
- pd->B::f();
- But this requires that g "know" the base class by name. The alternative (shown
- in Listing 5) is for D to define
- typedef B inherited;
- as a type member, so that g can call
- pd->inherited::f();
- Stroustrup [2] discusses this technique in greater detail. It only works if
- C++ compilers look up inherited in the scope of the class of *pd, namely D,
- using rule 2. Looking in the context of the call itself (rule 1) defeats this
- technique.
- The committees never found a convincing case for choosing one lookup rule over
- the other. Thus they decided that C++ should use both lookup rules, and that
- the combined result of both lookups should yield only one type. If the lookups
- yield conflicting results, the expression is ambiguous. The following is a
- more precise statement.
- In a postfix-expression of the form x.B::c, the translator looks for B as a
- type (looking only for types) in two contexts:
- 1. the class scope of x (that is, look up B as if it were a member of x), and
- 2. the context in which the entire postfix expression appears (the context in
- which the translator looked for x itself).
- The combined set of types found by (1) and (2) must have exactly one element;
- that element is the meaning of B. If the translator finds nothing, then it
- treats B as undeclared. If it finds more than one match, the reference is
- ambiguous.
- There is precedent for this approach to name lookup in other parts of C++.
- When a C++ translator encounters an expression of the form x @ y, where x is
- an object of a class type and @ is an overloadable operator, it looks for an
- operator@ that satisfies either
- x.operator@(y)
- or
- operator@(x, y)
- The search must find exactly one match. If it finds more the one, the
- expression is ambiguous.
- Now, at long last, I get to explain the extension itself. As part of
- clarifying the lookup rules, the committee decided to grant C++ programmers a
- little more control over name lookup for B in x.B::c. The extension allows
- postfix expressions of the form
- a.::B::c, a.::B::C::d, etc.
- p->::B::c, p->::B::C::d, etc.
- That is, C++ now allows :: after the . or ->. In such expressions, the
- translator evaluates B as if it appeared at the global scope. There's not much
- to the extension itself, once you get the background out of the way.
-
-
- Conversion from T **to const T *const *
-
-
- As a general rule, C++ (like C) allows implicit pointer conversions that
- increase const-ness, but not conversions that decrease const-ness. For
- example, given
- char *strcpy(char *s1, const char
- *s2);
- char a1[N], a2[N];
- the call
- strcpy(a1, a2);
- successfully converts the expression a2 from type char[N] to char *, and then
- to const char *. On the other hand, given
- const char ca1[] = "asdf";
- the call
- strcpy(ca1, a2);
- is an error because it requires converting ca1 from const char[5] to const
- char * (OK so far), and then to char *. The last conversion is an error
- because it tries to strip away a const qualifier. For that you would need a
- cast.
- Saying that C++ allows pointer conversions that increase const-ness is an
- oversimplification. Actually, C++ (like C) forbids certain pointer conversions
- that appear to increase const-ness. In particular,
- T ** => const T **
- is not safe, and C++ does not allow it. To see why this is unsafe, let's work
- our way through a progression of similar conversions. In the following, =>
- means "implicitly converts to."
- It's true that for any type T,
- T * => const T * //1a: OK
- safely increases const-ness. This much is clear. You cannot use the result of
- the conversion to corrupt a constant value. The const and T in //1a can appear
- in either order, so it's also true that
- T * => T const * //1b: OK
- Now, if you replace T in //1b with U *, you get
- U ** => U *const * //2: OK
- Since //2 is okay, it appears that
- T ** => const T ** //3: nope
- should be also, but it isn't. If C++ allowed //3, then you could accidentally
- change the value of a constant object, as shown by the following example.
-
- C++ cannot accept
- const char c= 'x';
- char *pc = &c; //4: error
- *pc = 'y'; //5: OK
- because it would change the value of c, which is supposed to be const. The
- error is not on //5; pc was declared to point to a non-const char. The error
- is on //4, because it tries to convert
- const char * => char * // no
- which decreases const-ness.
- Now, let's extend the example to:
- const char c = 'x';
- char *pc;
- const char **ppc = &pc; // 6: ?
- *ppc: &c; // 7: OK
- *pc = 'y'; // 8: OK
- This code also tries to change the value of c, but via a different route. The
- problem is not on //7, which simply copies one const char* to another. Nor is
- it on //8, which is the same as //5 in the previous fragment. No, the problem
- is that //6 opens a hole in the const safety net. If C++ (or C) allowed
- T ** => const T ** //3
- then it would have to allow //6.
- There is, however, a restricted conversion that is safe, namely,
- T ** => const T *const* // 9: OK
- This prevents the conversion on line //6 in the previous example. But is it
- still useful? In his paper proposing this extension, Andrew Koenig of AT&T
- showed that the conversion is useful with the following example.
- Given an array such as
- char *quintet[] =
- {
- "flute", "oboe", "horn", "clarinet",
- "bassoon"
- };
- you can compute the length of the longest string in such an array with a
- function declared as
- size_t maxlen(char **a, size_t n);
- Unfortunately, this function will not accept
- const char *quartet[] =
- {
- "violin", "violin", "viola", "cello"
- };
- because const char ** does not convert to char **. Thus, it appears that you
- must overload maxlen with another declaration:
- size_t maxlen(const char **a, size_t n);
- (You can't use this latter declaration in place of the original because
- passing quartet as the first argument requires conversion //3 above, which we
- already banished.)
- However, with the extension to allow the conversion
- T ** => const T *const *
- you can get by with just one declaration for maxlen, namely,
- size_t maxlen(const char *const *a, size_t n);
- In fact, this one definition even accepts
- const char * const trio[] =
- {
- "washtub", "jaw harp", "kazoo"
- };
- Listing 6 shows an implementation of this maxlen function that you can use for
- experiments.
- The committees actually approved a more general form of the conversion rule.
- Here's what the draft says:
- A conversion can add type qualifiers at levels other than the first in
- multi-level pointers, subject to the following rules:
- Two pointer types T1 and T2 are similar if there exists a type T and integer n
- > 0 such that:
- T1 is T cv1,n * . . . cv1,1 * cv1,0
- and
- T2 is T cv2,n * . . . cv2,1 * cv2,0
- where each cvi,j is const, volatile, const volatile, or nothing.
- An expression of type T1 can be converted to type T2 if and only if the
- following conditions are satisfied:
- the pointer types are similar.
- for every j>0, if const is in CV1,j then const is in cv2j, and similarly for
- volatile.
- the cv1,j and cv2,j are different, then const is in every cv2,k for 0<k<j.
- This means that the following conversions are also valid:
- T *** => const T *const *const *
- T **** => const T *const *const * const *
-
- and so on. So are:
- T *** => T **const *
- T const **** => T const **const *const *
- and even:
- T** => T volatile *const volatile *
- I'll describe more of these enhancements next month.
- References
- [1] Margaret A. Ellis and Bjarne Stroustrup. The Annotated C++ Reference
- Manual (Addison-Wesley, 1990).
- [2] Bjarne Stroustrup. The Design and Evolution of C++ Addison-Wesley, 1994).
- [3] P. J. Plauger. The Standard C Library (Prentice Hall, 1992).
-
- Listing 1 Using the dynamic_cast operator
- class B { ... };
- class D : public B { ... };
-
- void f()
- {
- B *pb = new D;
- ...
- D *pd = dynamic_cast<D *>(pb); // a downcast
- ...
- pb = dynamic_cast<B *>(pd); // a "normal" upcast
- }
- // End of File
-
-
- Listing 2 A nested class name hidden by a data member name
- struct A
- {
- struct B
- {
- static int n;
- } B;
- enum E { e = 1 } E;
- } A;
-
-
- Listing 3 Dealing with long class names using typedefs
- class VeryLongName
- {
- public:
- void f();
- ...
- };
-
- typedef VeryLongName VLN; // 1
- class A : public VLN // 2
- {
- ...
- };
-
- void g(A *ap)
- {
- ap->VLN::f();
- }
- // End of File
-
-
- Listing 4 A variation on the example in Listing 3
-
- class VeryLongName
- {
- public:
- void f();
- ...
- };
-
- class A: public VeryLongName
- {
- ...
- };
-
- typedef VeryLongName VLN;
-
- void g(A *ap)
- {
- ap->VLN::f();
- }
-
-
- Listing 5 Decoupling the class hierarchy structure from application code using
- a member typedef
- //
- // class library code
- //
-
- class B
- {
- public:
- void f();
- ...
- };
-
- class D: public B
- {
- public:
- typedef B inherited;
- void f();
- };
-
- //
- // library user's code
- //
-
- void g(D *pd)
- {
- pd->inherited::f();
- ...
- }
- // End of File
-
-
- Listing 6 A maxlen function intended for both arrays of constant strings and
- arrays of non-constant strings
- #include <iostream.h>
- #include <string.h>
-
- #define DIM(a) (sizeof(a)/sizeof(a[0]))
-
- char *quintet[] =
- { "flute", "oboe". "horn", "clarinet", "bassoon" };
-
- const char *quartet[] =
- { "violin", "violin", "viola", "cello" };
- const char *const trio[] =
- { "washtub", "jaw harp", "kazoo" };
-
- size_t maxlen(const char *const *t, size_t n)
- {
- size_t len = 0;
- size_t sl = 0;
- size_t i;
- for (i = 0; i < n; ++i)
- if ((sl = strlen(t[i])) > len)
- len = sl;
- return len;
- }
-
- int main()
- {
- cout << maxlen(quintet, DIM(quintet)) << endl;
- cout << maxlen(trio, DIM(trio)) << endl;
- return 0;
- }
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Questions & Answers
-
-
- Moving On
-
-
-
-
- Kenneth Pugh
-
-
- Kenneth Pugh, a principal in Pugh-Killeen Associates, teaches C and C++
- language courses for corporations. He is the author of All On C, C for COBOL
- Programmers, and UNIX for MS-DOS Users, and was a member of the ANSI C
- committee. He also does custom C/C++ programming and provides
- SystemArchitectonicssm services. He is president of the Independent Computer
- of the Independent Computer Consultants Association. His address is 4201
- University Dr., Suite 102, Durham, NC 27707. You may fax questions for Ken to
- (919) 489-5239. Ken also receives email at kpugh@allen.com (Internet) and on
- Compuserve 701215,1142.
-
-
- This will be my last Q & A column for the C/C++ Users Journal. My cat,
- Nameless, who sat on my lap while I typed my four books and many of these
- columns, died in November. I dedicate this last column to her.
- It's been an interesting time to write for a programming magazine. I created
- my first columns on an Osborne with an 8088, running Wordstar on CP/M. My last
- columns were written on a Thinkpad with a 486, running Wordstar in an MS-DOS
- window in Microsoft Windows. You can't teach an old dog new editing tricks.
- During the early days of this column, the ANSI C committee was established and
- the draft was approved as a standard The ANSI C++ committee started during the
- colunm's later days and its draft may be issued for voting soon.
- The C compilers used on the Osborne fit onto two floppy disks. Kernighan and
- Ritchie explained the entire language in a small book. The latest C++
- compilers I use on my Dell Pentium come on CD/ROM and take almost 100 MB of
- hard disk. The language and the interactions between its features have grown
- tremendously. For example, contemplating the ramifications of exception
- handling within a template that uses multiple inheritance from templated
- classes which themselves contain exception handlers is not a light task.
- I relinquish my column because I cannot move close to the speed of light and
- therefore cannot stretch out my ever shrinking time to continue answering your
- questions -- good questions that deserve attention I can no longer give. With
- the continuing spread of object-oriented programming and client/server
- technology, my teaching obligations have increased enormously. COBOL
- programmers are switching to C and C++ in droves. Demand for skills in UNIX,
- used for database servers, has dramatically grown. One publisher has been
- begging me for the last year for a book on the distributed computing
- environment. Another one wants a "Best of Questions and Answers."
- In April, I assume the presidency of the Independent Computer Consultant's
- Association. The ICCA is blossoming as corporations downsize, but still
- require the talent to produce their products. The anticipated growth of the
- association may engulf much of my remaining free time.
- In addition to time constraints, I have been slowly transforming my practice
- to what I call System Architectonics. Incorporating object-oriented analysis
- and design, its emphasis is on the interface to objects. Part of System
- Architectonics is the specification of that interface in a manner
- understandable to both the object designer and the object user. SA moves away
- from the actual coding in C/C++ to the abstraction of the design.
- Although I will not appear physically in these pages any more, I will still be
- present in the electronic world at 70125.1142@compuserve.com and eventually at
- kpugh@pughkilleen.com, which is currently being established.
- I wish my successor the best of luck in his column.
- Good-bye to all you C and C++ fans. See you in cyberspace.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- CUG New Releases
-
-
- Portable TAR, ED, LPC, and Two Updates
-
-
-
-
- Victor R. Volkman
-
-
- Victor R. Volkman received a BS in Computer Science from Michigan
- Technological University. He has been a frequent contributor to C/C++ Users
- Journal since 1987. He is currently employed as Senior Analyst at H.C.I.A. of
- Ann Arbor, Michigan. He can be reached by dial-in at the HAL 9000 BBS (313)
- 663-4173 or by Usenet mail to sysop@hal9k.com.
-
-
-
-
- Update to CUG #391: C/C++ Exploration Tools
-
-
- C/C++ Exploration Tools, by Juergen Mueller (Kornwestheim, Germany), includes
- both his C Function Tree Generator (CFT) and the C Structure Tree Generator
- (CST). CFT and CST analyze C/C++ source code, from applications of any size,
- contained in multiple files. CFT and CST are useful for exploring new, unknown
- software and for supporting reuse, maintenance, and re-engineering. By
- preprocessing, scanning, and analysing the program source code, these programs
- generate the function call hierarchy (CFT) and the data structure/class (CST)
- relations. Both programs can handle C and C++ code; CFT can additionally
- analyze assembler code. The C Exploration Tools v2.20, as MS-DOS executables
- (released 03/20/94), are immediately available as CUG volume #391.
- Version 2.20 of C Exploration Tools features significant enhancements over the
- previously released version 2.12. Here are just a few of the changes that
- Mueller reports:
- Microsoft Visual C++ 1.0 for Windows NT preprocessing support
- Borland C++ 1.0 for OS/2 preprocessing support
- A fix for CFT/CST database read errors that occured if line numbers were not
- in strictly ascending order inside functions or structures (a possibility if
- #line directives are used)
- A fix for CFT errors that occured with nested C++ constructs such as extern
- "C" { ... } (this was sometimes the reason for the error message 'unbalanced
- braces')
- Detection and display of multiple file inclusions (of the same include file),
- with scanned source size and number of lines calculated for multiple
- inclusions
- Support for the _TlMESTAMP_macro in preprocessors
- Option -Jcharset, which adds an extended character set for identifier
- recognition and allows the use of national character sets
- Option -//, which accepts C++ comments in C source code to ensure
- compatibility with Microsoft and Borland C compilers
- Option -RATIONAL, which generates a Rational Rose Petal file for callgraph
- visualisation (see documentation for detailed description!)
- A change to option -I that makes -I* ignore missing include files during
- preprocessing
- A new option -o[name] for CFTN and CSTN, which prints output to file 'name'
- Different return values for CFTN and CSTN to avoid conflicts with DOS errors
- (affects BRIEF macros)
-
-
- Update to CUG #363: MC68020 Cross-Assembler
-
-
- Andrew E. Romer (Worthing, West Sussex, England) updates his Motorola MC68020
- Cross-Assembler. This update completely replaces the earlier version of his
- work cataloged as CUG #363. Romer's MC68020 assembler was originally based on
- Paul McKee's MC68000 assembler (North Carolina State University). The latest
- version of Romer's assembler fixes a bug causing incorrect generation of S2
- records. Thanks to Eric M. Scharf (University of London, EE Dept.) for
- reporting it.
- A cross-assembler is an assembly language translator that runs on a different
- platform than the one it is assembling for. In this case, the MC68020
- cross-assembler runs on an MS-DOS machine (80x86 CPU) but reads assembly
- language for the MC68020 microprocessor and writes MC68020 object files.
- Typically, the developer then downloads these object files to the target
- machine (presumably a Macintosh, Atari, Amiga, or other MC68020 architecture
- machine).
- The cross-assembler supports constants in hexadecimal, binary, octal, and
- decimal. Constant expressions can use many C-style operators including bitwise
- operators. The following assembler directives are supported: END, ORG, EQU,
- SET, REG, DC (define constant), DCB (define constant block), and DS (define
- storage). The cross-assembler lacks support for native math coprocessor
- instructions, string constants, and macros. Documentation consists of a
- 12-page ASCII reference manual.
- The MC68020 cross-assembler for MS-DOS will compile under either Microsoft C
- or Zortech C. Version 1.01 (as released 03/04/94) is immediately available as
- CUG volume #363.
-
-
- New Acquisitions
-
-
- ED Editor (CUG #424): Multiplatform full screen text editor with windowing,
- regular expressions, and programming support features. Edits in ASCII, binary,
- or hexadecimal modes
- Portable TAR and LZPIPE (CUG #425): Multiplatform archiving tools that handle
- files, floppies, and QIC streamer tapes in tar, zip, gzip, or compress formats
- LPC-Parcor-Cepstrum code generation for C (CUG #426): Comprehensive libraries
- for audio data file normalization and manipulation
-
-
- CUG 424: ED Editor
-
-
- Charles Sandmann (Houston, TX) submits the ED editor with a user interface
- based on the DEC VMS EDT editor. ED is a true multiplatform editor and can be
- compiled and run on virtually any platform. It includes target-specific code
- for keyboard, screen, and TCP/IP handling. This design allows ED to run in
- UNIX (IBM RS/6000, Sun Sparc, HP, NeXT, or Alpha AXP machines), MS-DOS,
- Windows NT, and OS/2 environments with ease. ED can edit any kind of file in
- text, binary, or hexadecimal modes.
- Some of ED's more interesting features include the following:
-
- Multiple text windows
- Built-in file manager
- Editing by wildcards
- Calculator
- Automatic program indentation
- Parenthesis matching
- Box and columnar editing
- Insert and overstrike editing
- Sorting
- Load/save of files using FTP
- The ED documentation consists primarily of a 45-page ASCII help file. You can
- make this help file from within ED or using any standard text utilities you
- might have. The documentation assumes that you've had some exposure to the EDT
- editor that ED emulates or that you are willing to learn the basics.
- The CUG Library distribution of ED includes binaries built with the DJGPP
- edition of GNU C/C++ (MS-DOS with GO32 DOS extender). Also, this two-diskette
- set provides the full C source. Distribution and use of the ED source code is
- covered by the GNU General Public License (Version 2). ED version 1.5.7 (as
- released on 04/05/94) is immediately available as CUG #424.
-
-
- CUG #425A: Portable TAR
-
-
- Timor V. Shaporev (Moscow, Russia) contributes an extremely versatile version
- of the classic UNIX TAR archiver and an innovative method of delivering LZW
- compressed data over pipes. Portable TAR works with both MS-DOS and
- UNIX-compatible machines. Since more than half the source code available from
- the Internet appears in TAR format, you'll quickly find this a valuable
- utility. portable TAR reads and writes archives in ordinary files, raw
- floppies, and QIC-02 streamer tapes. It understands regular TAR formats,
- PKZIP, gzip, and UNIX compress.
- Portable TAR has several other advantages over most public domain TAR programs
- and those included with UNIX-compatible operating systems:
- Uniform processing across both MS-DOS and UNIX platforms
- Reading/writing of UNIX-compatible floppies and quarter-inch streamer
- cartriges under DOS
- Support for unusual floppy formats: 80-tracks-by-9-sectors and DEC Rainbow
- (under DOS)
- A data compression option under both DOS and UNIX
- System V and/or GNU multivolume archive read capability under DOS and all UNIX
- clones
- An option to restore damaged archives (and plenty of other useful options)
- As mentioned earlier, Shaporev claims source compatibility with most UNIX
- systems and MS-DOS. Specifically, he provides two makefiles that cover most
- UNIX implementations and another makefile for Borland Turbo C in MS-DOS. As
- you might expect, a small amount of assembly language code is provided for
- supporting functionality not normally found in MS-DOS.
- The CUG Library distribution of Portable TAR includes binaries built for
- MS-DOS. Portable TAR version 3.15 (as released on 04/05/94) is immediately
- available on CUG #425.
-
-
- CUG #425B: LZPIPE
-
-
- Shaporev's other contribution is the LZPIPE library, which implements the two
- most popular compression methods, LZW and deflate. Both of these methods are
- defacto lossless compression standards. LZW is used in the well-known compress
- utility and deflate is used by a number of others, starting from PKZIP by
- PKWare Inc. up to GNU GZIP.
- LZPIPE provides to systems like MS-DOS a programming capability analogous to
- UNIX pipes. It also allows access to compressed files, and provides a far
- simpler API than most compression utilities. Specifically, this library
- processes compressed data in the familiar file handle style of open, read,
- write, and close calls.
- LZIPE implements only pure compression -- no attempt is made to emulate ZIP
- directory services. Thus, you would either use LZPIPE to compress one file at
- a time or else add the extra functionality for multi-file archiving yourself.
- Source codes for LZW compression and decompression are derived from sources of
- the compress utility initially written by Joe Orost. Source codes for deflate
- compression and inflate decompression are derived from Info-Zip zip/unzip
- utilities sources. Inflate was initially written by Mark Adler and deflate
- originated with Jean-loup Gailly.
- The CUG Library distribution of LZIPE includes only C source code. As this is
- strictly a library, no MS-DOS binaries are included. LZPIPE version 1.01 (as
- released 04/05/94) is immediately available as part of CUG #425.
-
-
- CUG #426: LPC-Parcor-Cepstrum Code Generator
-
-
- Patrick Ko Shu Pui (Hong Kong) submits his LPC-Parcor-Cepstrum code generator
- for C. The LPC-Parcor-Cepstrum code generator (hereafter, LPC) will compile on
- most UNIX platforms as well as under Microsoft C/C++ 7.0 and Borland Turbo C
- v2.0. This archive's primary use is the manipulation and normalization of
- audio data files. Specifically, LPC supports eight-bit ulaw (SUN Sparc),
- eight-bit and 16-bit PCM data. LPC then generates LPC autocorrelation or
- covariance coefficients, Parcor (partial correlation) coefficients, or LPC
- cepstrum coefficients.
- LPC's implementation draws from algorithms and methods described by Shuzo
- Saito and Kazuo Nakata in Fundamentals of Speech Signal Processing (1985) and
- others.
- Astute CUJ readers will recall that Patrick Ko also contributed the Small
- Matrix Toolbox (CUG #403) earlier this year. In fact, the LPC application
- includes several key components from the Small Matrix Toolbox.
- The C source package included in LPC is free for academic purposes only. For
- commercial usage, you must send a US $30 money order addressed to the author
- (Patrick Ko). The CUG Library distribution includes full C source and binaries
- for MS-DOS. LPC version 0.52 (as released 04/16/94) is immediately available
- as CUG #426.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Editor's Forum
- One of the side effects of publishing a magazine on C and C++ is that people
- ask us a lot of questions. I personally get e-mail several times a week, on
- average, from people seeking help. Sometimes it's questions about standards
- conformance, sometimes about how to use some esoteric language feature. The
- harder ones to answer are those asking for recommendations on what compiler to
- buy, or peculiar features of some specific implementation. The really tough
- ones are questions about debugging some lengthy sequence of code.
- I answer all of my e-mail, even though that commitment often costs me an hour
- or more a day. The answers probably aren't always satisfactory -- I refuse,
- for example, to comment on the relative merits of commercial products. And the
- answers sometimes go astray. My DOS-based mailer can get bollixed by a long or
- esoteric return address. If you've written me in the past few years and
- received no reply to a direct question, know that I almost certainly tried to
- send you an answer.
- A more reliable way to get your questions answered is to send e-mail straight
- to R&D Publications. The folks who publish this magazine have better resources
- for tracking mail, and for finding someone who can answer it more or less
- wisely. For example, they know to pass Windows queries on to almost anybody
- else but me -- I still enjoy relative ignorance of that particular morass.
- One reliable source of answers lo these many years has been Ken Pugh. His
- Q?/A! column has fearlessly tackled a wide range of questions, usually with
- enlightening results. Often, Ken's explanations have stimulated even more
- detailed supplemental contributions from other readers. It is no surprise that
- Q?/A! consistently garners positive comments from our letter writers.
- Thus, I report with some sadness that Ken Pugh is retiring from his post as
- resident CUJ expert on almost everything. For some of the reasons why, you can
- read his farewell address later in this issue. My job is to thank him, on
- behalf of all you readers, for the yeoman effort he has put in for so long. He
- will be missed.
- That's not to say that we've run out of answers. CUJ continues to welcome
- questions, particularly those whose answers can help other readers as well.
- (Don't hesitate to ask because you think the question is too trivial --
- someone else is probably waiting for you to get the courage to ask it
- instead.) Keep those cards and letters rolling in, and we'll keep supplying
- folks who are willing to answer them patiently.
- P.J. Plauger
- pjp@plauger.com
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- New Products
-
-
- Industry-Related News & Announcements
-
-
-
-
- Liant C++/Views Supports Macintosh, SGI, Windows NT, and DEC Alpha OSF/1
-
-
- Liant Software has ported C++/Views, an application framework for developing
- multi-platform, native GUI applications in C++ to several additional
- platforms. C++/Views lets portable GUI applications be developed, and then
- deployed across multiple GUI platforms, without code modification. The new
- platforms supported by C++/Views include Macintosh, SGI, Windows NT, and DEC
- Alpha OSF/1. C++/Views also supports OSF/Motif (AIX, HP-UX, Solaris, and SUN
- OS), OS/2, and Windows.
- With C++/Views, developers can create one set of source code and resources for
- their applications, regardless of the number of platforms on which they want
- it to run. When compiled on each platform, the application will adhere to the
- native look and feel of that platform. Because the dimension of the display
- and user interface components on systems vary, C++/Views uses the Geometry
- Manager, a mechanism to preserve spatial relationships. The Geometry Manager
- automatically manages the size and portion of interface components within
- dialogs, letting the developer create an element and later resize it.
- C++/Views includes a library of 100 C++ classes including interface design,
- data management, event processing, and several higher-level interface classes
- including table, toolbar, and MDI classes. C++/Views also includes a visual
- development tool, C++/Views Constructor. C++/Views Constructor combines a
- class browser with a visual interface builder, which lets the user draw and
- test an interface, and then switch to editing the C++ source code within the
- same environment.
- The Windows/Windows NT, Macintosh, and OS/2 version of C++/Views are priced at
- $1,499. The SGI, DEC Alpha OSF/1, and other OSF/Motif versions are priced at
- $2,499. For more information contact Liant Software, Framingham, MA; (508)
- 872-8700.
-
-
- Tartan Acquires Energize and Lucid C/C++ Compilers
-
-
- Tartan Inc. has acquired the technology for Energize, the object-oriented
- development environment, and the C/C++ compilers from Lucid Inc. The Energize
- Programming System is a C/C++ development environment, which supports the code
- construction and maintenance phases of application development by providing an
- integrated, incremental development process. Lucid C is a production-quality,
- dual-mode compiler supporting ANSI C and K&R C. Lucid C++ is a C++ compiler
- that provides five modes of operation, including cfront compatibility and
- direct compilation of C++ code. The Lucid C capabilities are included in Lucid
- C++. Lucid C/C++ compilers operate on Sun SPARC and SPARC-compatible machines,
- running SunOS 4.1.x or Solaris 2.x.
- Tartan is offering Lucid customers a transition plan for maintaining existing
- investments without disruption. Current Lucid customers will be contacted
- concerning the specifics of the transition plan. Tartan is also organizing a
- new business unit to further develop, market, and support the Energize and
- C/C++ products.
- For more information contact Tartan, Inc., 300 Oxford Dr., Monroeville, PA
- 15146; (412) 856-3600; FAX: (412) 856-3636.
-
-
- MetaWare Announces High C/C++ Compiler with DirectToSOM C++ for OS/2
-
-
- MetaWare has announced the High C/C++ compiler with DirectToSOM C++ for OS/2.
- With DirectToSOM, C/C++ source code can be compiled with High C/C++ to create
- SOM-compatible binaries without writing Interface Definition Language (IDL)
- class descriptions. SOMobjects compiled with High C/C++ for OS/2 can
- interoperate with SOMobjects written in other languages.
- IBM's System Object Model (SOM) technology was developed to let programmers
- upgrade binary C++ libraries without having to recompile client source code.
- High C/C++ compilers include C++ exception handling (including nested
- exceptions), namespaces to avoid name clashes with third-party libraries,
- run-time type information (RTTI), and ANSI C++ casting notation to make C++
- typecasts safer than Standard C notation. High C/C++ also provides OMF common
- areas for templates, virtual function tables, RTTI, and inline functions. High
- C/C++ also includes "thread-safe" libraries that can be used in multi-threaded
- applications. All library functions are documented as to whether or not they
- support reentracy. In addition, High C/C++ includes switches for "fine-tuning"
- function inlining, optimizations to produce executables for specific
- processors such as the Pentium, and support for long long types.
- The High C/C++ DirectToSOMC++ for OS/2 compiler is priced at $595. For more
- information contact MetaWare Incorporated, 2161 Delaware Ave., Santa Cruz, CA
- 95060-5706; (408) 429-6382; FAX: (408) 429-9273; E-mail:
- techsales@metaware.com.
-
-
- ACI Ships Object Master for Windows
-
-
- ACI US, Inc. has begun shipping Object Master for Windows, an integrated,
- cross-platform programming tool for writing and organizing source code. Object
- Master for Windows lets C/C++ programmers write, edit, organize, and navigate
- through source code while using drag-and-drop Windows functions. Object Master
- integrates with Microsoft Visual C++, Symantec C++, Borland C/C++, and DOS
- compilers. Object Master can trigger compilations and receive errors without
- switching to the compiler environments.
- Object Master includes a source code editor, a project window, and a browser
- that lets users edit select pieces of code. In addition, Object Master has a
- Class Tree Window. Object-oriented programmers can view different parts of the
- Class Tree simultaneously by opening multiple windows.
- Object Master for Windows also parses each file as it is added to a project
- and includes it in a data dictionary. The data dictionary lists definitions
- and locations of named programming components such as classes, functions,
- fields, and data types. Once parsed, the information is automatically updated
- in the browser and editing windows. With a single keystroke, programmers can
- use template calls generated by Object Master to insert method or function
- arguments. In addition, Object Master color-codes and stylizes text for
- identification. Programmers can customize styles for each language element
- including functions, keywords, compiler directives, syntax errors, disable
- directives, and comments. Object Master also checks a file's syntax and lists
- errors in a window.
- Object Master for Windows is priced at $249. For more information contact ACI
- US, Inc., 20883 Stevens Creek Blvd., Cupertino, CA 95014; (408) 252-4444; FAX:
- (408) 252-4829; AppleLink: D4444.
-
-
- Popkin Software Announces OMT Support for System Architect
-
-
- Popkin Software & Systems, Inc. has announced that System Architect supports
- the OMT/Rumbaugh method of object-oriented analysis and design. System
- Architect supports integrated forward and reverse engineering for C++ code
- generation from designs created using the OMT/Rumbaugh methodology. Both
- skeleton files and C++ headers for an object-oriented application are
- automatically generated.
- Optional modules available for System Architect 3.0 include SA
- Object-Oriented, SA Schema Generator, SA Reverse Data Engineer, SA Screen
- Painter, SA Project Documentation Facility, SA PowerBuilder/Link, and SA/SQL
- Windows Link.
- In a related announcement, Popkin will ship upgraded support for
- object-oriented analysis and design techniques from Grady Booch and Peter
- Coad/Ed Yourdon to include changes in the most recent version. The upgraded
- support also includes the generation of skeleton files and C++ headers.
- Prices for System Architect 3.0 for Microsoft Windows range from $1,395 to
- $2,940. Prices for System Architect 3.0 for 0S/2 PM range from $1,795 to
- $3,790. Optional modules range in price from $495 to $1,995. Users of SA
- Object-Oriented with an active maintenance agreement will receive the
- OMT/Rumbaugh addition and the upgrades to Booch & Coad/Yourdon without charge.
- For more information contact Popkin Software & Systems, Inc., 11 Park Place,
- New York, NY, 10007-2801; (212) 571-3434; FAX: (212) 571-3436.
-
-
- Amzi! Upgrades Cogent Prolog
-
-
- Amzi! inc. has upgraded Cogent Prolog. Cogent Prolog is an Edinburgh-standard
- (non-typed) Prolog development system including a compiler, interpreter,
- interactive development environment, debugger, and royalty-free, distributable
- run-time module. The Prolog source and object code is portable across
- platforms. Cogent Prolog v3.0 includes a Logic Server API, which lets Windows,
- DOS, and Alpha programmers integrate Prolog's symbolic reasoning with
- conventional C/C++ applications.
- The Cogent Prolog Logic Server API provides 50 functions for interacting with
- Prolog code and data, and for extending the Cogent runtime to directly access
- GUI tool kits, database APIs, multimedia, or other facilities available to the
- C/C++ programmer. The API is available as a Windows DLL for use by a variety
- of C/C++ systems, as well as Visual Basic and Access; 16- and 32-bit,
- static-link libraries for use with Windows and DOS C/C++ tools; a C++ wrapper
- that makes the Logic Server an object; and a 64-bit DEC Alpha OSF/1 C/C++
- library.
-
- Cogent Prolog v3.0 is priced at $298 for DOS and Windows and $995 for OSF/1.
- Subsets of the full system are also available starting with the student
- interpreter for $49. For more information contact Amzi! inc., 40 Samuel
- Prescott Dr., Stow, MA 01775; (508) 897-7332; FAX: (508) 897-2784; Internet:
- amzi@world.std.com.
-
-
- Performix Announces Drag-It 2.0 and Drag-It/VBX
-
-
- Performix has announced two software add-on products: Drag-it v2.0 for Visual
- C++ and Drag-it/VBX for Visual Basic. Drag-it v2.0 includes a class library,
- starter files, "Drag-it Builder," and a sample application. Drag-it v2.0
- includes OLE support so that programmers may create applications in which
- symbols can be dragged from one Drag-it window to another. Drag-it diagrams or
- symbols can also be embedded into other Microsoft OLE containers such as
- Microsoft Word.
- The Drag-it class library extends the Microsoft foundation classes, and the
- Drag-it toolkit incorporates Drag-it interfaces into applications. Starter
- files assist the programmer in creating a skeleton application. Drag-it
- Builder is used to drag symbols and place them on a palette, or to import and
- place bitmaps there also. A sample application is included that shows both how
- to interpret Drag-it actions, and how to add a view to supplement information
- shown graphically by Drag-it.
- Drag-it/VBX provides Visual Basic programmers with a drag and drop front-end
- to their applications. Using Drag-it/VBX, programmers can create a Drag-it
- palette with symbols, or use Drag-it Builder to import bitmaps or draw
- symbols, or be notified of user actions by Drag-it/VBX Events. Also included
- in Drag-it/VBX is a sample application.
- Drag-it v2.0 is priced at $495. Drag-it/VBX is priced at $295. For more
- information contact Performix, 6618 Daryn Dr., Westhills, CA 91307; (800)
- 337-2448 or (818) 992-0840; FAX: (818) 347-9455.
-
-
- Inmark Ships zApp Developer's Suite for X/Motif
-
-
- Inmark Development Corporation has begun shipping the zApp Developer's Suite
- for X/Motif. The zApp Developer's Suite consists of the zApp Application
- Framework, the zApp Interface Pack, and zApp Factory. The zApp Application
- Framework is a set of C++ foundation classes. The zApp Interface Pack is a set
- of custom controls and high-level visual objects. The zApp Factory is a
- drag-and-drop application designer, code generator, and design testing
- facility for building user interfaces.
- The zApp Developer's Suite for X/Motif is available on Solaris 2, SunOS, IBM
- AIX, HP-UX, SCO UNIX, SGI IRIX, Novel UNIXware, and Sun Solaris x86. The zApp
- Developer's Suite for X/Motif is priced at $2,995 per developer. Source code
- for zApp and the zApp Interface Pack are included at no additional charge. For
- more information contact Inmark Development Corporation, 2065 Landings Dr.,
- Mountain View, CA 94043; (415) 691-9000; FAX: (415) 691-9099; E-mail:
- info@inmark.com.
-
-
- MultiQuest Releases S-CASE 2.0
-
-
- MultiQuest Corporation has released S-CASE 2.0. S-CASE uses the Booch notation
- to graphically illustrate and model software systems. C++ code is then
- generated automatically from the model. Features of S-CASE 2.0 include
- iterative C++ code generation, control over method and attribute order, #ifdef
- and macro support, class specification reports, printing of an entire model or
- category, printing from the integrated text editor, and a menu layout. Other
- features of S-CASE 2.0 include Booch's 1994 notation, real-time rule checking,
- C++ code generation, hierarchical project manager, heterogeneous multi-user
- support, and full color support.
- Platforms supported by S-CASE 2.0 include Microsoft Windows, Macintosh, Sun,
- and HP9000. S-CASE stores data in a platform-independent format allowing
- several users to work simultaneously on the same design from any supported
- platform.
- Prices for S-CASE 2.0 range from $495 to $995 depending on configuration.
- Floating licenses are also available. For more information contact MultiQuest
- Corporation, 1699 E. Woodfield Rd., Suite A-1, Schaumburg, IL 60173; (708)
- 240-5555; FAX: (708) 240-5556; E-mail: 72531.2510 @ compuserve.com.
-
-
- DataFocus Ships NuTCRACKER X/SDK
-
-
- DataFocus Incorporated has begun shipping NUTCRACKER X/Software Development
- Kit (X/SDK), a member of the NuTCRACKER family of UNIX-to-Win32 porting tools.
- NuTCRACKER X/SDK lets X and Motif-based GUI applications move to Windows NT.
- Rather than emulate UNIX on Win32, developers using NuTCRACKER X/SDK recompile
- their UNIX and X/Motif source code and link it to NuTCRACKER's DLLs, resulting
- in native Win32 applications. NuTCRACKER, which includes the MKS Toolkit
- utilities and a collection of UNIX calls based on SVR4, Berkeley, and POSIX,
- supports UNIX applications written in C/C++.
- For more information contact DataFocus Incorporated, 12450 Fair Lakes Circle,
- Suite 400, Fairfax, VA 22033-3831; (800) 637-8034 or (703) 631-6770; FAX:
- (703) 818-1532; Internet: nutcracker@datafocus.com.
-
-
- Blinkinc Releases Blinker 3.1
-
-
- Blinkinc has released Blinker 3.1, a royalty-free DOS extender, Windows and
- OS/2 linker, and DOS dynamic overlay linker, which offers incremental linking
- for protected-mode CA-Clipper programs. Features of Blinker 3.1 include
- protected-mode support for Microsoft C/C++ and FORTRAN graphics libraries,
- creation of Windows DLLs, dual mode support with internal or external overlay
- files, OS/2 support, and double the link-time virtual memory capacity for
- larger libraries and .EXE files.
- Blinker 3.1 requires a minimum of an 8086 microprocessor to link or run
- real-mode programs, while the DOS extender requires a minimum of an 80286
- microprocessor to run protected-mode programs, and is compatible with the
- DPMI, VCPI, and XMS specifications. Protected-mode programs created by Blinker
- 3.1 will run under Windows, OS/2, and DOS.
- Available for registered users to download from Blinkinc's BBS and CompuServe,
- Blinker 3.1 is also being sent on disk at no extra charge to subscribers of
- the Blinkinc Newsletter and Interim Upgrade Service. For more information
- contact Blinkinc, 8001 W. Broad St., Richmond, VA 23294; (804) 747-6700; FAX:
- (804) 747-4200; BBS: (804) 747-7333.
-
-
- Trinzic Announces Rational Rose/ObjectPro
-
-
- Trinzic Corporation and Rational Software Corporation have announced Rational
- Rose/ObjectPro, a graphical, object-oriented tool for analysis and design of
- client/server applications. Rational Rose/ObjectPro automates the production
- of ObjectPro code. ObjectPro is Trinzic's object-oriented development
- environment that combines C++ and Smalltalk.
- Using information in the Rational Rose/ObjectPro class diagrams, developers
- can generate ObjectPro source code files. These files contain ObjectPro code
- constructs that correspond to the notation items, classes, relationships, and
- adornments that have been defined in the model with Rose diagrams and
- specifications. Rational Rose/ObjectPro generates ObjectPro classes, instance,
- attribute, and method structures that define an ObjectPro application.
- Rational Rose/ObjectPro is priced at $1,495. For more information contact
- Trinzic Corporation, 555 Twin Dolphin Dr., Paragon Center, Redwood City, CA
- 94085; (415) 328-9595.
-
-
- AccuSoft Announces Pro-Imaging Toolkit
-
-
- AccuSoft Corporation has announced the Pro-Imaging Toolkit, a DLL which
- includes image processing and analysis support for 1-, 4-, 8-, and 24-bit
- images. The Pro-Imaging Toolkit supplies routines for accessing and modifying
- Windows DIB images. Routines in the Pro-Imaging Toolkit include chroma-key,
- region-of-interest processing, image analysis, image processing, color
- separation and combination, deskew, rotate and edge enhance, clipboard
- support, and graphics. The Pro-Imaging Toolkit provides access to both high-
- and low-level routines.
- Other features of the Pro-Imaging Toolkit include samples with source code and
- GUI support combined with a manual.
- The Pro-Imaging Toolkit is priced at $795 for 16-bit libraries and $1,495 for
- 32-bit NT. For more information contact AccuSoft Corporation, 2 Westborough
- Business Park, Westborough, MA 01581; (508) 898-2770; FAX: (508) 898-9662.
-
-
-
- IST and QNX Integrate X-Designer and QNX Realtime Operating System
-
-
- Imperial Software Technology has announced the integration of its Graphical
- User Interface builder, X-Designer, with the QNX Realtime Operating System
- from QNX Software Systems. QNX is a POSIX-certified, network-distributed
- operating system which is scalable from compact embedded systems to networks
- running hundreds of processors. X-Designer can be used to generate user
- interfaces in standard code for Motif, Windows, or Macintosh users.
- X-Designer provides geometry management capabilities, internationalization,
- HyperText Help, a compound string editor, and other features. X-Designer uses
- no proprietary libraries or code and can be integrated with third party
- products.
- For more information contact Imperial Software Technology, 95 London St.,
- Reading, Berkshire RG1 4QA, United Kingdom; +44 734-587055; FAX: +44
- 734-589005; E-mail: sales@ist.co.uk; or V. I. Corporation, Northhampton, MA;
- (413) 586-4144.
-
-
- Aladdin Introduces HASPCard
-
-
- Aladdin Software Security has introduced the HASPCard(tm). This PC expansion
- card combines a standard HASP software protection key with a parallel port,
- and provides enhanced security by having the key inside the PC. An internal
- connection makes HASP viable when external access to the parallel port is
- inconvenient or unavailable. Turnkey system vendors can now incorporate HASP
- protection as an integral part of their system. Finally, the internal HASPCard
- minimizes the risk that the card might be lost, damaged, or stolen.
- Since HASPCard functions like regular HASP keys, current HASP-protected
- applications will not need source code changes or recompilation to guarantee
- compatability with the card. Three types of HASP keys are available in
- HASPCard format: HASP-3, MemoHASP, and NetHASP.
- HASPCard is available for an increment of $18 per unit over current HASP
- implementations. For more information contact Aladdin Software Security Inc.,
- The Empire State Bldg., 350 5th Ave., Suite 7204, New York, NY 10118; (800)
- 223-4277 or (212) 564-5678; FAX: (212) 564-3377.
-
-
- LEAD Ships LEADTOOLS Visual Basic/VBX 4.0
-
-
- LEAD Technologies has begun shipping LEADTOOLS Visual Basic/VBX 4.0, a
- royalty-free toolkit, which allows software developers to add image
- compression and manipulation to any Visual Basic, Visual C++, or any other
- application that provides a Visual Basic interface.
- LEADTOOLS VBX handles checking file formats, reading files, compressing or
- decompressing data, and displaying images. The VBX includes a property to link
- to all databases supported by Visual Basic such as Access, dBase, Clipper, and
- Fox Pro. The toolkit supports over 42 file formats including Kodak PhotoCD,
- PCX, GIF, TGA, BMP, TIFF, JPEG, JFIF, JTIF, and LED CMP, LEADTOOLS can also be
- used as a Windows DLL, a DOS executable, or a Visual C++ or Basic custom
- control. LEADTOOLS supports both 16-and 32-bit Windows development.
- For more information contact LEAD Technologies, Inc., 900 Baxter St.,
- Charlotte, NC 2804; (704) 332-5532; FAX: (704) 372-8161.
-
-
- General Imaging Announces S/IP 80
-
-
- General Imaging has announced a single-slot, form-factor SBus card, providing
- up to one billion operations per second. Follow-on versions targeted for first
- quarter of 1995 and 1996 will run at 1.6 and 2.0 billion operations per
- second, respectively. The S/IP 80, is an SBus C80 card for signal- and
- image-processing applications, built around Texas Instrument's TMS 320C80, and
- bundles General Imaging's signal/image processing framework, ProtoPIPE.
- ProtoPIPE provides a graphical programming environment for object-oriented
- graphic designs targeting the C80 chip for such applications as radar,
- remote-sensing, GIS, medical, and machine vision.
- The S/IP80 hardware includes a multi-standard video-digitizer, capable of
- capturing live video and performing real-time processing. An additional audio
- CODEC provides support for sound processing applications. The complete
- hardware/software package is priced at $27,000.
- For more information contact General Imaging Corporation, 6 Fortune Dr.,
- Manning Park, Billerica, MA 01821; (800) 255-1577 or (508) 262-2262; FAX:
- (508) 262-0088.
-
-
- Cascadilla Press Lowers Price of Help Browser License
-
-
- Cascadilla press has lowered the price of a developers license for Help
- Browser 2.0. Help Browser is a utility which makes it easier to find
- information in Windows 3.1 help files. Help Browser provides an interactive
- map of the help file, allows for word and phrase searches, and supports
- printing multiple topics.
- With a developer's license for Help Browser, developers can add the features
- of Help Browser to their Windows 3.1 application help files for distribution.
- Control over the interactive map and text search tools can be customized. When
- distributing Help Browser with an application, you include a special version
- of the Help Browser along with a database file created by the full version of
- Help Browser.
- A developer's license for Help Bowser 2.0 is priced at $179 and includes the
- right to distribute an unlimited number of copies of the embeddable version of
- Help Browser. The end-user version of the Help Browser, which works with
- Windows 3.1 help files, is priced at $89 and site licenses are available. For
- more information contact Cascadilla Press, P.O. Box 440355, Somerville, MA
- 02144; (617) 776-2370; FAX: (617) 776-2271; E-mail: info@cascadilla.com.
-
-
- COSMIC Announces COSMIC C Cross Compilers
-
-
- COSMIC Software, Inc. has announced the COSMIC C cross compilers for
- Motorola's 68HC11 and 68HC16 microcontrollers. Highly optimized, ANSI C and
- ISO standard compliant, COSMIC C cross compilers also include header file
- support for on-chip peripherals. Other features of COSMIC C cross compilers
- include compile-time type checking, C run-time support, extensions to the ANSI
- standard designed for embedded systems, support for 68HC16 memory models, and
- C source-level cross debugging.
- The COSMIC C compiler package includes: an optimizing C cross compiler, macro
- assembler, linker, librarian, object inspector, hex file generator, object
- format converters, debugging support utilities, run-time library source code,
- and a multi-pass compiler command driver. Prices for the COSMIC C cross
- compilers start at $1,500. For more information contact COSMIC Software, Inc.,
- 100 Tower Office Park, Woburn, MA 01801; (617) 932-2556; FAX: (617) 932-2557.
-
-
- Lenel Systems Announces MediaRecorder Toolkit
-
-
- Lenel Systems International has announced the MediaRecorder Toolkit. With
- MediaRecorder Toolkit, Windows developers can integrate video capture
- capabilities into their applications. With MediaRecorder Toolkit's OLE
- automation support, OLE controls, C++ libraries, and DLLs, developers can
- integrate MediaRecorder as a component into their multimedia applications.
- MediaRecorder Toolkit includes a video capture component and an image capture
- component. The video capture component provides methods for real-time digital
- video captures, single video frame capture to a variety of graphics formats,
- video thumbnails, and digital video compression. The image capture component
- provides methods for capturing graphics files in formats such as BMP, JPEG,
- TIF, GIF, PCX, TGA, and DIB. MediaRecorder Toolkit also includes palette
- capture, still frame capture into any industry-standard file format, resizable
- windows, and support for Windows-compatible video capture boards.
- MediaRecorder Toolkit is priced at $595. There is no runtime license fee for
- non-commercial products. For more information contact Lenel Systems
- International, Inc., 290 Woodcliff Office Park, Fairport, NY 14450-4212; (716)
- 248-9720; FAX: (716) 248-9185.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- We Have Mail
- Dear Editor,
- Bjarne Stroustrup says of reference arguments (The C++ Programming Language,
- 2nd Ed. p. 62), "However, to keep a program readable it is in most cases best
- to avoid functions that modify their arguments." Dan Saks, however, says
- (C/C++ Users Journal September 1994 p.89)
- int genq::remove(void *&e) {
- ...
- e = p->element;
- ... }
- Richard Clarke says I don't much care who's right but agreement needs to be
- made one way or the other.
- Yours faithfully,
- Richard A. Clarke
- R.Clarke@ee.surrey.ac.uk
- There are indeed two schools of thought on functions with side effects. A pure
- functional programming style views side effects within a function call (or
- indeed within any expression) as dangerous. The argument is that the code has
- surprising effects not directly discernible from reading the expression that
- calls the function. C/C++ programmers, in general, are more pragmatic. They
- cheerfully write autoincrement operators, embedded assignments, and function
- calls with side effects -- whatever gets the job done with an economy of
- notation and executable code. Stroustrup seems to be arguing for a tempered
- version of pure functional programming. Saks shows the valid use for reference
- parameters (as opposed to const reference parameters). Programming practice is
- too diverse for me to see a serious contradiction here.-- pjp
- P. J. Plauger, Editor, C/C++ Users Journal:
- Have you ever needed a rewind function for C++ istreams? The usual alternative
- is the less elegant pair of calls:
- istrm.seekg(0);
- istrm.clear();
- Instead, I use a rewind manipualor, which I define in the file rewind.h (see
- Listing 1).
- The rewind manipulator does for C++ istreams what C's rewind function does for
- C file streams. Note that rewind can be used with any descendant of istream
- but is most appropriate with ifstreams.It's used like this:
- #include "rewind.h"
- ...
- ifstream InputStream("filename");
- InputStream >> a >> b >> c;
- // read three items from InputStream
- InputStream >> rewind >> a2 >> b2 >> c2;
- // rewind and read three more items
- // note: a==a2 && b==b2 && c==c2
- In the above example, a and a2 are extracted from the same part of InputStream
- (as are b and b2, and c and c2).
- Please share this code with your readers if you think they'll find it useful.
- (The code also serves to demonstrate one way to define C++ manipulators and
- how easy it can be.)
- Paul Hepworth
- s165r@cc.usu.edu
- Yes, manipulators have endless possibilities. To comply with the draft C++
- Standard, you have to modify this in several small ways, but the idea is
- basically a good one. -- pjp
- Editor,
- Thank you for a wonderful magazine! I have been a reader for many years and a
- subscriber for several. I get several C-oriented publications off the shelf on
- an irregular basis, but I have found that only your magazine has enough
- information to the general programmer to subscribe to.
- Thank you also for the fax-back service offered from the magazine. Many of
- these are available for various publications, but they are all long-distance.
- I hope that you can continue offering this service to your readers in the
- future. (I understand that someone must foot the bill, and if it's not me,
- it's probably your publication.) If you could pass on my thanks to those
- businesses who support this service I would appreciate it! At my day job, my
- office can afford the long distance calls, but for my after-hours programming
- and personal work, I cannot. Some of these advertisers may recognize my name
- or my company's name as information obtained through this service has led to
- several purchases for myself, and for my office.
- This service is wonderful for the struggling small business that watches every
- penny. I have recently spent a fair amount of money to get my home business
- started on the right foot and I would not have been able to get as much as I
- did, if it weren't for this service!
- Once again, I thank you for providing this service, and I thank your
- advertisers for participating!
- Jeffry Brickley
- Lockheed Engineering and Sciences (by day)
- TimeWarp Software (by evening)
- Dear P.J.Plauger,
- I am writing a C++ program for Windows using Borland C++ 4.0. The program
- makes use of a large tree of small objects, which is built during
- initialization. After initialization the tree is read-only during the rest of
- its lifetime. The program worked fine until the size of the tree approached 64
- KB; seemingly, I ran out of memory. Under Windows memory is split into "near
- heap" (near pointers, total size < 64 KB) and "far heap" (far pointers, total
- size: all free memory). The problem arose because I used the C++ operator new
- to allocate objects. new works only on the near heap. It returns a near
- pointer (void near*), and you cannot overload new to return a far pointer
- (void far*). I am not sure how general the problem is, but my solution may be
- of general interest.
- To solve the problem objects must be allocated on the global heap, of course,
- paying the penalty of accessing the objects via far rather than near pointers.
- I must find ways: (i) of managing the global heap and (ii) of allocating
- objects there. With regards to (i), a very simple implementation is possible,
- because all objects are created at initialization time and remain unmodified
- until the end of execution. (See Listing 2.)
- Function New returns a far pointer to a memory block of the requested size.
- You cannot specifically delete such a block, rather the whole Heap object goes
- up in flames as its destructor is called. GlobalAllocPtr and GlobalFreePtr are
- Windows functions for maintenance of the global heap. To find the address at
- the offset pos bytes within the current block, the starting address is cast to
- a pointer to character. The pos added will then count as bytes.
- To address (ii), I have this suggestion for allocating objects of some class X
- on the global heap:
- extern Heap heap;
- class X {
- ...
- virtual X far* Clone()
- { X far* p =heap.New(sizeof(X));
- *p = *this;
- return p;
- }
- };
- The Clone function simply makes a copy of the X object on the global heap and
- then returns a pointer to that copy. The copy constructor of X should be
- implemented to ensure that the assignment above works out. If deriving a class
- Y from X, it should have an implementation of Clone() similar to this.
- Otherwise, X::Clone() would be called for Y objects with disaster soon to
- follow.
- The above definitions could be used like this:
- // Global heap object
- Heap heap;
-
-
- main(){
- // Three far pointers to X;
- // initialized in different ways (i-iii):
- X far* pa, far* pb, far* pc;
-
- // (i) From a local object
- X a("Rip", 2);
- pa = a.Clone();
-
- // (ii) From an object on near heap
- X near* b;
- b = new X("Rap", 3);
- pb = b->Clone();
- delete b;
-
- // (iii) From a temporary object
- pc = X("Rup", 7).Clone();
- }
- Any comments on my design in general or on heap management under Windows in
- particular are welcome.
- Yours,
- Niels Holst,
- Zoological Institute,
- University of Copenhagen,
- Denmark
- E-mail: NHo1st@zi.ku.dk
- Unless I'm missing something, it seems to me that placement new is a better
- mechanism for this sort of thing. -- pjp
- Dear Editor:
- I have an application for which I would like to generate random English
- pronounceable proper names. I have looked in the common sources (Knuth,
- Sedgewick, Wirth, etc.) for a suitable algorithm, but haven't had much luck.
- Have you ever come across such an algorithm or one that could be adopted?
- Sincerely,
- Edward B. Bell
- Integrated Solutions, Inc.
- 6618-18 Reafield Drive
- Charlotte, NC 28226
- No, but I can think of some puckish applications for such an algorithm. I hope
- one of our readers can help you find one. -- pjp
- Dear CUJ,
- There is a bug in Listing 1 on page 78 of your April 1994 volume. The loop
- control variable i is incremented within the initialization loop with the
- result that only even indices of the freq_count array are set to zero. Also,
- errors will occur if characters are signed and strings containing characters
- with ordinal values 80-FF hex are input, since this will yield negative
- indices into the freq_count array.
- Sincerely,
- John S. Hanus
- Houston TX, 770
- P.S. Would you please tell me how I might obtain a recent salary survey for
- computer professionals?
- Thanks for the bug report. You might try one or more of the IEEE publications.
- Seems to me they publish regular surveys covering our field. -- pjp
- Dear Mr. Plauger,
- When I saw an article about an extended date library in your October, 1994
- issue, I was hopeful. There is just so much disinformation these days about
- calendars and dates that I had hoped this article would clear up some of the
- confusion. Unfortunately, I was somewhat disappointed after reading the
- article.
- In the article, Mr. Milam explains that his date function "returns the number
- of days elapsed since 1 January, 0001 A.D." Such a statement implies that the
- date function is valid all the way back to that date. I often find myself
- dealing with dates in the Middle Ages, so this looked like something I could
- use.
- Being skeptical, however, I decided to check the accuracy of that claim,
- specifically for dates before October 15, 1582, the date the Gregorian
- calendar went into effect. (For practical purposes, this is true even though
- some countries did not accept the Gregorian calendar until several centuries
- later. Regardless of when the calendar was accepted, there is a discontinuity
- caused by the removal of ten days so that Easter would fall closer to the
- spring equinox. In the Catholic Church, this discontinuity occurred between
- October 4th and October 15th, 1582.)
- To his credit, Mr. Milam does mention in a sidebar that "This value presumes
- the Gregorian calendar in use today was implemented on 1 January, 0001 A.D.,
- (which of course, it was not)..." No mention of this is made in the main
- article, however, which might cause some people to be misled into thinking
- that the date function can be used for calculations, such as day of the week,
- and days between dates, regardless of what side of October 15, 1582 the days
- fall. In fact, the assertion that the value of date is the number of days
- since January 1, 0001 A.D. is false even for days after October 15, 1582
- because before this date, the Gregorian calendar and the Julian calendar
- should be identical.
- As an example, date will return a value of 728,202 for October 1, 1994, but
- the correct value is 728,203. The difference is due to errors in two places.
- The first, as noted before, is due to the discontinuity in the Gregorian
- calendar and the different method of calculating leap years before that date.
- In order to compensate for this, the following adjustments must be made for
- dates occurring after October 4, 1582:
- Adjustment for leap centuries divisible by 400: --(1582 / 400) --3
- Adjustment for leap centuries divisible by 100: +(1582 / 100) +15
- Adjustment for dates removed from calendar: --10
- Cumulative adjustment: +2
- The other error is in not adjusting for January 1, 0001 since, by definition,
- zero days would have elapsed between this date and itself. I therefore
- subtracted one to obtain the correct result. In order to obtain the correct
- result for dates after October 4, 1582, the result should therefore be
- increased by one. For dates before this, the year calculation should omit the
- adjustments for leap centuries and then one should be subtracted from the
- result to account for January 1, 0001.
- The modified code would appear as shown in Listing 3.
- While sidebar articles are a nice way to delve into related subjects, I
- sometimes don't read them unless I'm interested in more background
- information. In my opinion, information which relates to the correct use of a
- program or procedure library should appear in the main text of the article.
- Even with all of my complaints, I am glad to see someone publishing something
- about dates and calendars. This is practical information we all can use. I
- look forward to seeing more articles of this kind in the future.
- Sincerely,
- Michael R. Kabala
-
- Davenport, IA 52807
- Every time I think I have nothing new to learn about calendars... Thanks. --
- pjp
- Greetings:
- What is this that I see in the July 1994 issue of CUJ? Why it's a YASM (yet
- another style manual). Complete with the imperative shall. I guess we are
- doomed to repeat history endlessly.
- Some 15 years ago I began writing code with curly braces in two languages --
- Ratfor and Pascal. The curly braces were used for entirely different reasons
- in the languages and it created no small amount of confusion amongst my Pascal
- colleagues when I would show them my Ratfor code. And, naturally, curly braces
- were not treated at all kindly by the Fortran crowd.
- So, I took a defensive tack and put some macros in the wrapper for my Ratfor
- source code that defined begin as { and a variety of end statements (end_for,
- end_do, end_if, end_while, etc.) as }. While I was at it, I equated the ==, !=
- &&, // notations to Fortran like symbols eq, ne, and, or respectively, and !
- to NOT. Moreover, the if-then-else craze was in so I defined and used an empty
- then statement at the end of my if statements. My Fortran friends settled down
- some; my Pascal pals seemed mollified.
- When I migrated to C in 1982 I took most of this baggage with me. Using this
- stuff, I talked several assembler fanatics into trying C. What really turned
- them on were the ++ and -- operators. One of the benefits of using
- lettered-operators was that my supervisors, who typically knew little or no C,
- could read my code. I wrote thousands of lines of C code this way.
- Then, in 1986 I moved to an organization where C was the preferred language.
- The programmers were, by and large, C strict constructionists. To them, what I
- had written simply wasn't C at all. Well, a hybrid maverick dialect maybe --
- but unacceptable. So I ended up writing a tool in lex to convert my lovely
- code (KB's stuff, they called it) to regular C. I converted thousands of lines
- of my library utilities.
- The point in all this palaver is that I have come to the conclusion that it is
- preferable to stick to the language as provided and let the uninitiated
- readers and bystanders fend for themselves. As for team members, if they are
- carrying excess baggage from another language, they can hoist it overboard in
- time. Moreover, if new members to the team are experienced C programmers,
- their learning time is shortened.
- Successful software projects are the result of sound programming principles.
- Careful, thoughtful, thorough design, coding and checkout will do more for a
- programming project that all of the synthetic paste-ons that the new idealists
- love to promulgate.
- Sincerely,
- K. B. Williams
- Stillwater, OK
- I agree with most of what you say, with an added proviso. Whatever a
- programming group settles on in the way of a uniform style is always better
- for the group effort than any individual's notion of good style. , -- pjp
- Dear Mr Plauger,
- Apparently I'm one of the remaining few who find C's attractions outweigh its
- many distractions. Even at this late date, I find myself keeping an eye out
- for books and articles on C in the hope that yet another of its hidden truths
- may surface, intentionally or unwittingly revealed at last. I do this because
- I see in C a language whose real potential lies rooted in its remarkable if
- somewhat inchoate, capacity to support novel, even advanced programming
- techniques and dialects.
- Unfortunately this aspect of C is rarely touched on publicly. C's popularizers
- tend to spend their time re-hashing its syntax and precedence rules or they
- make a career out of reminding us of the tricks and traps any respectable
- construct is capable of entertaining. Occasionally they even go on and on
- about the intricacies of the command-line, mentioning its limited relevance
- for today's world.
- Project-sized program development is where it's at today, moving the
- programmer's concerns so far beyond the command line that it's almost become a
- fossil. For one thing, we no longer test from the command line with debug
- options unless we're nearly brain-dead in the first place.
- So what then if while testing your latest fantabulous brain-child, your
- computer goes idling off into infinity, hobnobbing with chaos, dancing the
- herky-jerky (formerly known as the black death), what else is new? If you know
- what you're doing within a matter of minutes you've located the trap-door
- through which that errant blankety-blank fell.
- You're able to do that because the limbo of faulty technique is, itself,
- flawed. It can't even rear its ugly head for a last malicious leer without
- inadvertently revealing its odiferous self. That's because design problems
- (apparently with teleological foresight) have always excelled at setting what
- appear to be bottomless-pit booby-traps, even though they actually aren't.
- The rumor persists that they exceed code problems as breeders of man-eating
- bugs, so we must at least continue pretending to respect them. Just be
- careful, though, where you seek help when you think what you've got is a code
- problem. Unless you're working on someone else's grossly over-extended
- project, are trying to mesh with the other guy's buggy code, or are voyaging
- into a complex, new (is this trip really necessary) environment, you'll find
- debuggers largely irrelevant. If you can learn to avoid the swamps of fantasy
- land, you'll rarely need one.
- That doesn't mean we've all suddenly become omniscient. Most programmers (if
- they're any good at all) do serve a life-long apprenticeship. At least, they
- do as far as the analysis and design stages of program development are
- concerned; but if they wallow in the myriad ways to code this or that
- construct, they're rarely very productive. And yes, there are always beginners
- thirsting for knowledge of the basics, but the day they monopolize anyone's
- readership to the exclusion of all else, someone's in more trouble than they
- realize.
- That may be why it's a good idea for any programmers' periodical to include
- coverage of the big picture; if not regularly at least frequently. I don't
- think, for example, that I'd need the fingers on more than one hand (not even
- another digit or two) to count the number of times I've come across a
- treatment anywhere of the semantics of programming in general, let alone that
- of C.
- Many of us, believe it or not, have only an occasional or quickly passing
- interest in bit manipulation, pointer arithmetic, multi-level indirection,
- type conversion tricks, porting, porting, porting, etc, etc, etc. We deal with
- these matters rarely but carefully; once we've found reliable,
- results-oriented methods for handling them, that's it. We definitely ain't
- preoccupied with or troubled by them no more.
- And certainly, none of us has to write many programs in any language before we
- realize we've bigger fish to fry if we're to become proficient. Even beginners
- soon grasp the significance of learning the system requirements for putting
- together multi-module projects. (Another subject I've never seen treated
- adequately anywhere.)
- Of course, acquiring the skills needed for tackling larger projects does
- entail, among other things, learning to handle memory management, which in
- turn requires one to become agile at sidestepping any guru gobbledegook espied
- along the way, especially the kind designed to persuade the gullible they're
- facing another of those ubiquitous, "mystery wrapped in an enigma" boogeymen
- the Elect erect around every turn in the road.
- It also means learning to juggle a motley crew of I/O knives, plates, and
- bowling balls with considerable skill and dexterity (now there's some cans
- waiting to be opened).
- It doesn't hurt a bit either if, somewhere along the way, one works up a
- minimally comprehensive, rock-solid library of heap-oriented Abstract Data
- Types (perhaps some of those very ADTs so summarily dismissed from public
- scrutiny when OOPing first appeared on the scene). Without them, even in C
- only a limited level of sophistication is possible.
- Of course, an alternative to using your own ADT library is to use C++. But
- replacing your ADTs with C++'s Ptolemaic version of the OOP-iverse can do
- strange things to your view of the night sky. True, C++ does have its merits.
- It also has its share of leaky balloons, sluggish ships of the OOP-osphere,
- unable to stay aloft without a lot of huffing and puffing by the latest
- version of the cutting-edge.
- Suppose we did elect to use C++ selectively and rarely, preferring instead to
- develop our own in-house C dialects, would that mean the language might soon
- find itself teetering on the edge of the Babelization precipice? Not to worry.
- C's been perched precariously on the next ledge over for years. Who among us
- hasn't already found it annoying (or worse) to have to read radically
- different styles of perfectly legitimate C code on a regular (almost daily)
- basis?
- (Let's let he who cast the first stone... My guess is he's probably gonna do
- it anyway.)
- Quickly now, do you know of any other programing language in the whole
- computerized world that lets you almost completely redo itself for your own
- nefarious purposes? Where else can you erect a highly articulate, linguistic
- super-structure of your own design without risking the wrath of The Great Pooh
- Bah? Isn't this what programming at its most fascinating and fullfilling is
- all about? After one strips away the vestiges of commercialism, with its
- transient aura of achievement and reward (even earthly power for those on an
- egotrip), what's left?
- Haven't we always entertained (secretly of course) a yen to use our computers
- as extensions of our minds? Well, C is one programming language (par exellence
- at that) which allows us to do just that. It beckons us to explore and invent.
- It invites us to create new, viable alternatives (ultimately perhaps, even one
- to supplant itself).
- When you get right down to it, I'm sure this has a lot to do with why I still
- count myself among the remaining (lucky) few.
- Truly,
- Mark Rhyner
- Uh, yeah. Thanks for sharing those thoughts. -- pjp
- Gentlemen:
- I have discovered a bug in the code for your program dump.c on page 70 of the
- September 1994 issue of The C/C++ Users Journal. The following line of code:
- fprintf(stderr, "Can't open %s\n");
- should read as follows:
- fprintf(stderr, "Can't open %s\n",
- argv[1]);
- Otherwise the file name is displayed as machine garbage.
- I have been a faithful reader/subscriber to The C/C++ Users Journal for over
- five years now and a C user for over seven years, although at this point I
- still just dabble in the "basement." I have found your magazine to be quite
- useful and have saved every issue since I became a subscriber. I felt I should
- bring the aforementioned program bug to your attention.
- Sincerely,
- David P. Williams
- Thanks. -- pjp
- Dear Mr. Plauger:
- With interest I read the letters of Mr. Singleton and Mr. Larsson concerning
- the memory allocation within Windows 3.[01] in January issue of C/C++ Users
- Journal.
- Isn't it interesting how something like malloc can be screwed up in this
- wonderful OS? I usually program in the dated and complicated OS UNIX where I
- simply call malloc and don't have to worry about segmented architectures. I
- wonder why people think that Windows is such a great improvement over other
- operating systems. Could it be that they really mean is it is an improvement
- over DOS?
- Agreed, it is nice to have all of these colorful icons and windows. Agreed, it
- is easy to use as long as it does not hang up, destroy five hours of work or
- anything like that. However it is a pain in the neck when it comes to
- programming it.
- I once had to port a UNIX library to Windows and believe me, I think it is a
- very good reason to get out of the programming business altogether and start
- selling newspapers or do anything that makes sense.
- Sincerly,
- Detlef Engelbrecht
- Hamburg, Germany
- detlef@isys.net
- A segmented architecture certainly adds complexities, particularly for a
- language like C that evolved on machines with flat address spaces. We can hope
- that the emerging 32-bit flavors will eventually end the need for several
- flavors of pointers, and storage allocators to go with them. -- pjp
- Editor:
-
- I hereby add my voice to those who enjoy CUJ, and wish we had found it sooner.
- (And, being slow to convert, would like the scales tipped in the direction of
- C)
- The "Conversions and Casts" article by Chuck Allison in the September 1994
- issue of CUJ was excellent. Articles of this type provide review and
- instruction to us all.
- I would question one section of the article (and I may be misreading what he
- is saying). In the Arithmetic Conversions section, Mr. Allison states that
- "The roundoff error inherent in finite-precision arithmetic caused the product
- 100 * 23.4 to land closer to 2339 than 2340." C does not round when it
- converts from a float to an int, it truncates.
- Because, as Mr. Allison points out, floating point numbers are only
- approximations and not exact (the degree of approximation being
- machine-dependent), the result of a floating-point multiplication of 100
- (which is implicitly promoted to a float) and 23.4 is represented in the
- machine as a fraction less than 2340.0. Therefore, when the result is assigned
- to an int, the precision is truncated, and only the integer portion is
- assigned. The reason the conditional statement inside the printf also fails to
- recognize the result to be 2340.0 is again because of the result being an
- approximation which is compared against a constant exact value of 2340.0.
- My apologies if I have misread the text of the article, but I thought it
- needed to be made clear.
- George B. Durham
- Your explanation is accurate enough. I believe, however, that Chuck Allison
- was using "roundoff" in the most general sense. "Rounding to lower" is a fancy
- term for truncating, and "rounding to nearest" is another term for the
- colloquial rounding. -- pjp
- Dr. P.J. Plauger:
- I just read your January review of Anthony Porter's book, The Best C/C++ Tips
- Ever and I can't tell you how impressed I was at the way you shredded this
- Porter character. I'll bet he was really surprised.
- I would have been. For months your magazine has carried full-page ads for a
- software giant who sells a buggy linker, a buggy debugger, and now it's
- revealed (thanks to some of your competitors) even a buggy compiler.
- Meanwhile, the vendor's customers are advised to call the company's $2.00 per
- minute advisor line if they have complaints. What really surprises me is that
- your apparently overwhelmingly strong sense of ethics hasn't compelled you to
- mention something about maybe as many as one of these problems.
- Mr. Porter's book was published by McGraw-Hill, while I notice that my copy of
- your book The Standard C Library was published by Prentice-Hall. That probably
- has no relevance to anything, but I'm trying to understand why you choose to
- kill flies with a hatchet while you spray perfume on nuclear hooligans.
- Perhaps you'd like someone to believe that because of your hatchet work you
- really must be some sort high principled, rock-ribbed he-man editor. To me, it
- indicates both a lack of principles and testicles.
- Sincerely,
- Bob Moore
- cc: McGraw-Hill Publications
- Your thorny prose demands a few well chosen responses. First, please note that
- I did not shred "this Porter character." I shredded his book, which stands or
- falls on its own merits. Ad hominem attacks say more about the attacker than
- the target, as a rule. So too does a person's ability to distinguish between
- criticism of things and people.
- Second, I have personally written a number of books and several commercial
- compilers, so I'm keenly aware how hard it is to do both. I am still actively
- writing books and software, so I have scant time for much anything else. If I
- review a book instead of a major software package, that speaks more to the
- relative effort I can spare than to the relative importance a third party
- might ascribe to the two efforts.
- Finally, I have to confess that I care not one whit what you think about me or
- my motives. Anthony Porter tried to write a good book and failed, at least in
- my estimation. I respect that effort far more than I respect bystanders who
- sling mud with neither restraint, accuracy, nor a sense of direction. -- pjp
- Dear Mr. Plauger:
- Since there has been so much interest in date routines lately, I could not
- resist putting in my two cents worth.
- The routine presented by Mr. Magalhaes in your January 1994 issue is, in fact,
- a rendition of the formula presented by the great mathematician C.F. Gauss (1,
- 2) which has the acknowledged limited domain of years. A more robust method
- for calculating the date of (western) Easter which is valid for any date in
- the Gregorian calendar, that is from the year 1583 on, is given in Listing 4.
- I came across this method in a book by Meeus (3) in which he states that this
- method was originally given by Spencer Jones in his book General Astronomy
- (1922). It was then published again in The Journal of the British Astronomical
- Association, vol. 88, page 91 (December 1977) where it is said that this
- method was devised in 1876 and appeared in Butcher's Ecclesiastical Calendar!
- The routine in Listing 4 can be made more efficient by using the divide with
- remainder function instead of the repeated divisions. I merely present it in
- this format as it may be easier for some to follow. For Those who are
- interested, Listing 5 shows a method for calculating Julian Easter. It is
- interesting to note that the date of Julian Easter has a periodicity of 532
- years.
- Well, my two cents are now up. Thanks for letting me babble and keep up the
- good work. I enjoy the magazine very much.
- References: (3) and (4) are general references for Gauss (and others)
- [1] Gauss, C. F., Theory of the Motion of Heavenly Bodies (Dover Reprint,
- 1963)
- [2] Reicharot, Hans, ed., C. F. Gauss, Leben und Werk (Gauss Gedenkband,
- Berlin: Haude & Spener, 1960)
- [3] Meeus, John, Astronomical Formulae for Calculators (Willmann-Bell, Inc.,
- 1988)
- [4] Boyer, Carl B., A History of Mathematics (John Wiley & Sons, 1968)
- [5] Bell, E. T., Men of Mathematics (Simon and Schuster, 1937)
- Aris Karimalis
- The two functions certainly have an elegant simplicity. Thanks. -- pjp
- Editor,
- (Pardon the informality, but) would you mind solving one simple office puzzle
- for us in a future CUJ article/box? We regularly exit Windows to log users out
- or in (allowed only in DOS, not a shell) to our Novell-based LAN at work.
- Isn't there a procedure we could compile and place in batch to simplify this
- clunky process?
- Thanks,
- Skip Pletcher
- MenSana
- I don't know squat about programming for Netware, but I know what you want
- must be possible. My Netware Lite manager will execute a DOS batch script from
- within Windows that can, among other things, log me onto the network. Anybody?
- -- pjp
-
- Listing 1 A rewind manipulator for istream
- /* rewind.h vers 0.9
- defines istream manipulator rewind
- that "rewinds" input stream to
- beginning of file and clear state
- (to reset eof flag)
-
- copyright 1994 Paul Hepworth
-
- Permission is granted for everyone
- to include this code in his/her
- programs without restriction.
- */
- #if !defined_REWIND_H
- #define_REWIND_H
-
- #if !defined _IOSTREAM_H
- #include <iostream.h>
- #endif
-
-
- class rewind_type
- {
- friend istream& operator>>(istream& is,
- const rewind_type);
- };
-
- const static rewind_type rewind;
-
- inline istream& operator>>(istream& is,
- const rewind_type)
- {
- is.seekg(0);
- is.clear();
- return is;
- }
- #endif
-
-
- Listing 2 Allocating global memory from windows
- const unsigned BLOCKSIZE = 60*1024;
- const int MAXBLOCK = 64;
-
- class Heap{
- void far* block[MAXBLOCK] // Array of far
- // pointers to
- // memory blocks
- int n; // No. of blocks
- // in use
- unsigned pos; // Next vacant
- // position within
- // current block
-
- public:
- Heap()
- { n = 0;
- }
- ~Heap()
- { for (int i = 0; i<n; i++)
- GlobalFreePtr(block[i]);
- }
- void far* New(unsigned nBytes)
- {
- // Allocate another block if necessary
- if (n == 0 nBytes > BLOCKSIZE - pos){
- assert(n < MAXBLOCK && nBytes <= BLOCKSIZE);
- block[n++] = GlobalAllocPtr(GHND, BLOCKSIZE);
- assert(block[n-1] != 0);
- pos = 0;
- }
- // Get a pointer from within the current block
- void far* p =
- ((char far*) block[n - 1]) + pos;
- pos += nBytes;
- return p;
- }
- };
-
- Listing 3 Code to correct Milam's Date Routines
- static int is_it_a_ieap_year( unsigned year ){
-
-
- if (year > 1582)
- return ((year % 4 == 0 && year % 100 != 0)
- year % 400 = 0);
- else return year % 4 = 0;
- }
-
- static date_t years to_days( unsigned year) {
-
- date_t rv;
-
- if (year > 0) year--,
- rv = year * 365L + year / 4L;
- if(year>= 1582)rv+=year/400L-year/ 100L+ 12;
- return rv;
- }
- date_t time_to_date( time_t tv )
-
- date_t rv;
- struct tm *tm;
- int year, leap_year;
-
-
- /**********************************************/
- /* Get a time structure to use for conversion
- process. */
-
- /*********************************************/
-
- tm = localtime(&tv);
-
-
- /*********************************************/
- /* Use values in the tm structure to convert
- the current */
- /* date into a long integer value. */
-
- /*********************************************/
-
- year = tm -> tm_year + 1900;
- leap_year = is_it_a_leap_year(year);
- rv = years_to_days( year );
- rv += months_to_days(tm -> tm_mon + 1,
- leap_year );
- rv += tm->tm_mday;
- if((year> 1582) ((year== 1582)&&
- ((tm->tm_mon > 10) 11 ((tm->tm_mon = 10)
- && (tm->tm_mday > 14))
- rv-= 10;
- return rv - 1;
- }
-
-
- Listing 4 Calculating Gregorian Easter
- /* Gregorian Easter */
-
- int geaster (int year)
- {
- int A, B, C, D, E, F, G, H, I, K, L, M, Month, Day;
-
-
- if (year < 1583) return 0;
- A = year % 19;
- B = year / 100;
- C = year % 100;
- D = B / 4;
- E = B % 4;
- F = (B + 8) / 25
- G = (B - F + 1) / 3;
- H = (19 * A + B - D - G + 15) % 30;
- I = C / 4;
- K = C % 4;
- L = (32 + 2 * E + 2 * I - H - K) % 7;
- M = (A + 11 * H + 22 * L) / 451;
- Month = (H + L - 7 * M + 114) / 31;
- Day + (((H + L - 7 * M + 114) % 31) + 1);
- return (Month + Day * 10);
- }
- /* unwrap using Month = result % 10 */
- /* Day = result / 10 */
-
-
- Listing 5 Calculating Julian Easter
- /* Julian Easter */
-
- int jeaster (int year)
- {
-
- int A, B, C, D, E, Month, Day;
-
- A = year % 4;
- B = year % 7;
- C = year % 19;
- D = (19 * C + 15) % 30;
- E = (2 * A + 4 * B - D + 34) % 7;
- Month = (D + E + 114) / 31;
- Day = ((D + E + 114) % 31) + 1;
- return (Month + Day * 10);
- }
- /* unwrap using month = return % 10 */
- /* day = return / 10 */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- An Error Manager With Message Text Replacement
-
-
- David Chapman
-
-
- David Chapman is a Software Consultant specializing in VLSI CAD tools,
- compilers, and language design. He has a BSCS from California Polytechnic
- State University, San Luis Obispo, and an MSEE from Stanford University. He is
- currently working on a research project in VLSI layout synthesis. He may be
- reached at dchapman@netcom.com.
-
-
-
-
- Introduction
-
-
- Library developers often face a difficult problem: the need to report errors
- without knowing what kind of environments the library routines will be used
- in. For example, VLSI ComputerAided Design (CAD) software often runs under a
- graphical user interface (GUI), based on the X-Window system. My code usually
- does straight number crunching with little user interaction. However, VLSI CAD
- software sells worldwide, wherever there is a local semiconductor industry. I
- want to be able to translate my software to the language used by my customers
- even though I don't speak any languages other than English (so far). Thus my
- code needs a simple way to translate the error messages it prints.
- The more commonly-used techniques for making programs configurable don't
- support this need very well. Macintosh and Windows programmers are familiar
- with resource files, which contain the printable text and some of the graphics
- used by a program. Resource editors allow users to change the text associated
- with error messages and prompts. Resource files exemplify a "heavyweight"
- system, requiring a fair amount of source code and auxiliary files. Such a
- system requires editing and checking in of both the source code and the
- message file. On a project with only one message file being maintained by
- multiple programmers, updates may be lost unless extreme care is taken.
- I've maintained code based on other message systems that export the text, but
- I've found them difficult to manage. Here's a sample message from one of those
- systems:
- raiseWarning(runOutShape,baseName,cellName);
- Since the original author didn't comment this particular message, I had to go
- look up the text so I could understand the code that used it.
- Finally, in a CAD environment, the same code may run within a GUI or as a
- non-graphical program, selected at run-time, so even conditional compilation
- isn't enough. In such an environment, it's a good idea to pass all messages
- through a single centralized location. This makes it easier to enforce message
- presentation standards as well as to redirect error messages: I don't want to
- print to stderr while a GUI is running!
-
-
- Designing an Error Manager
-
-
- My CAD software is destined for a UNIX environment, so Windows and Macintosh
- resource tools won't help. Also, I'm working alone, so I need a system that's
- easy to develop and use. My design goals are as follows:
- 1. easy replacement of message text
- 2. multiple replacement dictionaries (for partial replacement or subsystems)
- 3. automatic error checking of replacement strings
- 4. 100% reliability and functionality even in the absence of replacement
- dictionaries
- 5. easy re-use of low-level library code that prints error messages
- 6. code readability during maintenance
-
-
- General Approach
-
-
- I route all error and warning messages through a single error manager. I don't
- try to handle dialogues here, because graphical and text-based interfaces
- often differ substantially, and my software isn't very interactive. However, I
- can replace prompts as well.
- My system stores the default messages in the source code, so they can document
- some of its intent (after all, the user must be able to understand the
- message). Linking the program incorporates the messages into the program
- directly, so there are no resource files to get lost. (However, performing a
- translation requires a message dictionary, which is a separate file containing
- replacement text.)
- Keyed error messages in the source code begin with a '$' followed by an
- alphanumeric message name and a colon. The error manager strips off these keys
- before printing the message (See Listing 1 for an example of a keyed message).
- If the error manager finds a message corresponding to the key name in the
- message dictionary, and the dictionary's parameter order is compatible with
- the original, then the error manager uses the replacement message from the
- dictionary. If it finds any errors in the replacement text, the manager prints
- a warning and uses the original text from the source code.
- I based the system on fprintf because it's very difficult to modify
- streams-based I/O to fit this approach. Every string constant in an output
- statement would require its own key, and formatting could not be changed. If
- you despise fprintf and switched to streams as soon as you learned C++, you're
- probably better off with a resource editor.
-
-
- Message Dictionaries
-
-
- Message or complaint dictionaries store replacement text for the keyed error
- messages. If a message has a keyed name, the dictionary manager retrieves the
- replacement text and the error manager compares it with the program text. The
- error manager then walks down both message bodies comparing the format
- specifications. If the two disagree, the error manager assumes the program
- text is correct and prints a warning message. The error manager also lists the
- key name so that the user can fix the dictionary text.
- This process demonstrates the advantage of storing the original message in the
- source code: even if the dictionary files are damaged or deleted, the program
- can still print the original message text. The user might complain about
- messages suddenly appearing in English, but I consider this better than
- crashing with no message whatsoever.
- Some fprintf format specifiers are equivalent, or at least read same-size
- objects from the argument list. The function format_specifier in errmgr.cpp
- returns a "canonical" format character for each type. For example,
- format_specifier converts all floating-point numbers to double for fprintf, so
- the 'e', 'f', and 'g' types all map to the same letter. The user can change
- the way values are printed (more digits of precision), but the type ordering
- is fixed in the program.
- Each logical line in a message dictionary contains one message. Newlines are
- quoted with '\' and comment lines begin with '#'. The dictionary format is the
- same as the message format except that the leading '$' is omitted. (This is
- not program text, so you don't need to quote special characters.)
- Note that an implicit newline resides at the end of each message, even if the
- program text does not contain one. If you use this system to retrieve prompt
- text using err_mgr.message, you'll want to strip off this newline or else the
- user's input will be on the next line.
-
-
- Message Handlers
-
-
- The error manager handles four message types:
-
- 1) Fatal -- the program prints the message and exits.
- 2) Serious -- the program asks for permission to continue.
- 3) Warning -- the message is printed and the program continues.
- 4) Posting -- the message is simply printed.
- The error manager routes all messages through a handler, which can be replaced
- at run time. For example, the startup code of a GUI would install a graphical
- error reporter (printing to a pop-up box, for example) and the termination
- code would restore the previous handler. All error handlers are based on the
- failure_handler class (see Listing 2).
- The default failure_handler prints to stderr except for the post routine,
- which prints to stdout. The routines themselves are fairly simple (see Listing
- 3). The other routines in failure_handler appear on this month's source code
- disk.
- The class error_mgr defines the error manager. Application code calls the
- member functions of this class's one-and-only instance, err_mgr. err_mgr then
- directs the message to the handler currently defined. (See Listing 4.)
- Handlers stack; define_handler returns the previously defined handler to allow
- a sort of run-time inheritance. For example, an instance of the class
- counting_failure_handler, when created, will install itself in the handler
- stack and then tally each serious error or warning before passing the message
- to the previous handler. The counting_failure_handler will remove itself from
- the handler stack when it is destroyed. Listing 5 shows how
- counting_failure_handler installs and removes itself from the stack.
- The error_mgr class uses counting_failure_handler when reading dictionaries.
- The complaint_dict constructor (Listing 9) parses the dictionary file,
- reporting errors through the warn function. Class complaint_dict is shown in
- Listing 8. If complaint_dict reports any problems the error manager knows the
- dictionary is invalid. This technique gets around the "constructor returns no
- value" problem. Of course, if another failure_handler is stacked on top of a
- counting_failure_handler, errors won't be tallied unless that handler also
- passes its messages downward. Only the topmost handler receives messages.
- Listing 7 shows class ptr_stack, which manages the handler stack.
- A program can use multiple error dictionaries. define_dictionary parses the
- named dictionary and returns 1 if it finds no errors. If define_dictionary
- finds errors the error manager ignores that dictionary. I pass in a filename
- rather than an open file because file descriptors are a precious commodity. To
- replace a message, the dictionary manager reopens the file and rereads the
- message using the stored file offset. If a message appears in more than one
- dictionary the last one defined takes precedence. Listing 6 shows function
- define_dictionary, as well as other member functions of class error_mgr.
- There are two error_mgr routines for each type of error. The second, similar
- to vfprintf, accepts a va_list argument. I found out the hard way that it's
- not a good idea to overload these names; if va_list resolves to (char *) (e.g.
- in Borland C++), then the call
- err_mgr.warn("Can't open %s\n", filename);
- may be routed to the va_list version of the function instead! (ARM section
- 13.2 says "sequences that involve matches with the ellipsis are worse than all
- others.")
- The message function searches for the replacement text for the fmt argument
- and, if found, stores it into the buffer passed along. message then returns a
- pointer to the start of this buffer or within the fmt argument if no such
- message is found so that the result can be used within an fprintf call (e.g. a
- prompt). In this demonstration version, the buffer length is passed in; for
- robustness all routines of this sort should use a buffer object that grows
- when text is added instead.
-
-
- Debugging Features
-
-
- Last but not least this error manager provides an assertion capability.
- Assertions are always compiled into the code; the macro NDEBUG controls only
- the default value of the assertions_off function. The ASSERT macro is similar
- to assert except that it first checks err_mgr.assertions_off. An assertion
- failure is a serious but not necessarily fatal error in this system.
- err_mgr.set_assert_flag(0) sets err_mgr.assertions_off, short-circuiting all
- assertion evaluations. Since it's an inline function returning the value of a
- static member value, set_assert_flag doesn't slow down assertion evaluation.
- In the worst case assertions can slow execution up to 20%, so users might not
- want assertions on unless the program is prone to crashes.
- Note that all of the private variables are all static integers or pointers. In
- errmgr.cpp these variables are all initialized to zero, because the ARM states
- that assignments to zero are performed first. Global constructors are called
- in an implementation-specific order, so if an error occurs in a constructor
- that executes prior to the err_mgr constructor, err_mgr must set itself up.
- Thus every routine in errmgr.cpp, including the constructor, calls setup
- first, directly or indirectly.
- setup allocates all of the non-simple variables off the heap to ensure they
- are properly initialized as well.
-
-
- Automated Message Extraction
-
-
- Once you've completed your application, you'll want to build the message
- dictionaries automatically. This month's code disk contains source code for a
- scanner written for flex, the fast replacement for the UNIX lex program. The
- scanner searches for string constants (merging adjacent string constants, of
- course) that appear to be keyed error messages.
- I didn't try to write a C++ parser, or even to locate all err_mgr calls,
- because some of my mid-level utility routines implement their own error
- logging systems on top of err_mgr. For example, a parser will often print the
- language context in addition to the error message. Thus I'd have to analyze
- the program to determine that calls such as
- lexer->complain("$illegal_char:" "Illegal character. \n");
- were in fact calls to err_mgr. At worst the scanner will find a few extra
- "messages." Note that the scanner won't find keyed messages constructed at run
- time; they would be difficult for users to translate anyway.
- The scanner in its current form requires flex because it uses some features
- that only flex provides. [A public-domain version of flex that can be compiled
- under both MS-DOS and UNIX is available from the C User's Group Library. See
- ordering information at the end of this article. --mb] I've included the
- output C file as well so that you can at least compile the program. The code
- that actually generates the dictionaries is in findmsgs.c, so if you want to
- change the format of the messages or dictionaries you won't need to edit the
- scanner itself.
-
-
- Message Database Management
-
-
- With every release of your software you will probably need to rebuild the
- message dictionaries, because you will most likely add new messages and modify
- old messages. If your customers (or a group within your company) have
- translated all of the messages into another language, you'll need to help them
- with the update. I recommend building a new set of dictionaries for the
- release, then comparing them message-by-message with the previous release.
- This process will result in a list of messages added or changed by the
- programmer. Customers must then write or edit translations for these messages
- into another language, you'll need to help them with the update. I recommend
- building a new set of dictionaries of the release then comparing them
- message-by-message with the previous release. This process will result in a
- list of messages added or changed by the programmer. Customers must then write
- or edit translations for these messages.
- If your documentation or support groups are modifying message text (for
- example, to ensure consistent wording), then you'll need to compare the source
- code messages in the current release with the edited dictionaries. This step
- will give you a list of messages that the support groups have modified, to
- merge with the programmers' modifications.
-
-
- Limitations
-
-
- Naturally, any system based on fprintf is subject to parameter typing
- problems. Without a full C++ parser, plus program knowledge to determine which
- calls will be routed to the error manager, you can't guarantee against
- printing occasional garbage. Streams have an obvious advantage here, but again
- text replacement is a problem with them.
- Currently, you can't edit messages in a way that rearranges the order of the
- parameters. At most you can rearrange the text around the parameters. You
- would have to define a whole new fprintf-styleformat and write routines to
- extract the parameters from the argument list in their original order, then
- shuffle them to meet the new requirements. It's not a simple task, so I didn't
- even try.
- My CAD software is rather large, straining the limits of MSDOS, so I don't
- keep the replacement text in memory. You could modify the dictionary manager
- very easily to keep the message text around so that it wouldn't have to
- re-open the file every time a message was requested.
-
-
- Limitations in This Version
-
-
- My production error manager uses a managed buffer that grows in length
- automatically as text is added to it. This demonstration implementation uses
- fixed-length (usually 512 characters) buffers instead. For safety you should
- use some kind of expanding buffer. I've marked the source code to show where
- the fixedlength buffers are used. Look for variables named linelen and the
- associated comments.
- For speed, keyword lookup should use a hash table. I've used a singly-linked
- list here for simplicity.
- This month's code disk contains the following:
- fully commented source for all modules and the C++ scanner
- test programs for error_mgr, complaint_dict, ASSERT
- makefiles for Zortech C++ 3.l, Symantec C++ 6.00, Borland C++ 4.00, and UNIX
- (the latter untested)
-
- findmsgs.exe (the scanner) compiled using Zortech C++
- All code described in this article is placed in the public domain. You may use
- it as you wish.
-
-
- Acknowledgements
-
-
- The flex scanner is based on one that Tony Sanders (once upon a time at
- cs.utexas.edu!ibmaus!auschs!sanders.austin. ibm.com!sanders) wrote in 1990.
- I've extended it to support floating-point numbers and quoted characters
- within string constants.
-
-
- Obtaining a Version of Flex
-
-
- Daniel R. Haney (MA) has ported flex to MS-DOS. Flex is available in CUJ
- Library volume 290. The disk contains a complete set of source code,
- documentation, a makefile, and a word count program as an example. Haney's
- implementation of flex can be compiled under MSDOS and UNIX. An OS/2
- executable is included.
- Also check out Flex++, CUJ volume 405. For more information, contact:
- R&D Publications
- 1601 W 23rd, Suite 200
- Lawrence, KS 66046
- (913)-841-1631. FAX: (913)-841-2624
- e-mail: michelle@rdpub.com.
-
- Listing 1 A keyed error message passed to the error manager
- err_mgr.fail("$edif_keyword: EDIF production %s"
- "resolved to unknown keyword %s\n",
- prod->keyword(), newkeyword);
-
-
- Listing 2 Definition of class failure_handler
- /* message handler: fail() is for fatal errors, */
- /* error() is for serious errors, warn() is for */
- /* warning messages to stderr, and post() is for */
- /* ordinary messages to stdout. */
-
- class failure_handler {
- public:
- failure_handler(void) {}
- virtual void fail(const char *fmt,va_list ap);
- virtual void error(const char *fmt,va_list ap);
- virtual void warn(const char *fmt,va_list ap);
- virtual void post(const char *fmt,va_list ap);
- private:
- /* unimplemented: */
- failure_handler(const failure_handler &other);
- failure_handler
- &operator =(const failure_handler &other);
- };
- // End of File
-
-
- Listing 3 A sample member function of class failure_handler
- void failure_handler::error(const char *fmt,va_list ap)
- {
- fputs("ERROR: ",stderr);
- vfprintf(stderr,fmt,ap);
- if (!ok_to_continue())
- exit(EXIT_FAILURE);
- }
- // End of File
-
-
-
- Listing 4 Definition of class error_mgr
- /* errmgr.hpp fragment */
-
- class error_mgr {
- public:
- error_mgr(void);
- ~error_mgr(void);
- int define_dictionary(const char *filename);
-
- /* returns handler just hidden */
- failure_handler *define_handler(
- failure_handler *new_handler);
- /* returns handler just popped */
- failure_handler *restore_handler(void);
-
- void fail(const char *fmt ,...);
- void vfail(const char *fmt,va_list ap);
- void error(const char *fmt ,...);
- void verror(const char *fmt,va_list ap);
- void warn(const char *fmt ,... );
- void vwarn(const char *fmt,va_list ap);
- void post(const char *fmt ,... );
- void vpost(const char *fmt,va_list ap);
-
- const char *message(const char *fmt,
- char *msg_line,int len);
-
- int set_assert_flag(int asserts_on);
- static int assertions_off(void)
- { return asserts_off; }
- int assert_failed(
- const char *exp,const char *fname,
- unsigned linenum);
- private:
- void setup(void);
- static failure_handler *curr_handler,
- *default_handler;
- static int is_setup,asserts_off;
- static error_dict_list *error_dicts;
- static ptr_stack *handler_stack;
- int find_replacement(
- const char *key,char *msg_line,
- int linelen);
- /* unimplemented: */
- error_mgr(const error_mgr &other);
- error_mgr &operator =(const error_mgr &other);
- };
-
- extern error_mgr err_mgr;
-
- #define ASSERT(e) \
- ((void)(err_mgr.assertions_off() \
- (e) \
- err_mgr.assert_failed(#e,_FILE_,_LINE_)))
- // End of File
-
-
- Listing 5 Member functions of class counting_failure_handler
-
- counting_failure_handler::
- counting_failure_handler(void)
- {
- /* install ourselves as the current handler */
- /* when we are created. */
-
- error_logged = warn_logged = 0L;
- prev_handler = err_mgr.define_handler(this);
- }
-
- counting_failure_handler::
- ~counting_failure_handler(void)
- {
- /* unlink ourselves from the handler chain */
- /* when we are destroyed. */
-
- failure_handler *top = err_mgr.restore_handler();
- /* no other handler should be stacked over us! */
- ASSERT(top == this);
- }
-
- void counting_failure_handler::error(const char *fmt,
- va_list ap)
- {
- ++error_logged; /* tally problems */
- prev_handler->error(fmt,ap); /* pass message on */
- }
- // End of File
-
-
- Listing 6 Member functions of class error_mgr
- /* errmgr.cpp - error manager */
- /* has been trimmed to fit; see the monthly code */
- /* disk for the complete source. */
-
- #include <stdio.h>
- #include <ctype.h>
- #include <stdlib.h>
- #include <stdarg.h>
- #include <string.h>
- #include "complain.hpp"
- #include "utils.hpp"
- #include "c_failur.hpp"
- #include "errmgr.hpp"
-
- /* an element in the list of complaint dictionaries: */
-
- class error_dict_list {
- public:
- error_dict_list(error_dict_list *curr,
- complaint_dict *new_dict);
- error_dict_list *prev;
- complaint_dict *dict;
- };
-
- error_mgr err_mgr;
-
- /* is_set_up is set when err_mgr is initialized. no */
- /* other error manager can be active. */
-
-
- int error_mgr::is_set_up = 0);
-
- /* asserts_off is 0 when assertions are to be checked. */
-
- #ifndef NDEBUG
- int error_mgr::asserts_off = 0;
- #else
- int error_mgr::asserts_off = 1;
- #endif
-
- failure_handler *error_mgr::curr_handler = 0,
- *error_mgr::default_handler = 0;
- error_dict_list *error_mgr::error_dicts = 0;
- ptr_stack *error_mgr::handler_stack = 0;
-
- static char line[512]; /* should be managed buffer */
-
- static int proper_format(const char *pgm_text,
- const char *user_text);
- static char format_specifier(const char **fmt);
-
- error_mgr::error_mgr(void) /* constructor */
- {
- setup();
- }
-
- error_mgr::~error_mgr(void) /* destructor */
- {
- delete default_handler;
- delete handler_stack;
- }
-
- void error_mgr::setup(void)
- {
- /* create default handler and empty dictionary */
- /* list, allocate stack for failure_handlers, */
- /* make sure no other error_mgr is present. */
- if (this != &err_mgr) /* just in case... */
- err_mgr.fail("Duplicate error_mgr defined.\n");
- if (is_set_up)
- return;
- is_set_up = 1;
- curr_handler = default_handler =
- new failure_handler;
- handler_stack = new ptr_stack;
- }
-
- int error mgr::define_dictionary(const char *filename)
- {
- /* add a new dictionary to the list. it's */
- /* ignored if any errors are found. */
- counting_failure_handler *our_handler;
- complaint_dict *new_dict;
-
- setup();
- /* we count errors in the file with our_handler. */
- our_handler = new counting_failure_handler;
- new_dict = new complaint_dict(filename);
-
- if (our_handler->errors_logged()
- our_handler->warns_logged()) {
- delete new_dict;
- warn("$bad_error_dict: dictionary file \"%s\" "
- "has errors - will not be used\n",
- filename);
- delete our_handler;
- return 0;
- }
- error_dicts = new error_dict_list(
- error_dicts,new_dict);
- delete our_handler;
- return 1;
- }
-
- failure_handler *error_mgr::define_handler(
- failure_handler *new_handler)
- {
- /* install a new error handler, pushing the */
- /* previous handler onto a stack. */
-
- failure_handler *old_handler;
-
- setup();
- handler_stack->push(curr_handler);
- old_handler = curr_handler;
- if (new_handler == 0) /* bomb-proof */
- curr_handler = default_handler;
- else
- curr_handler = new_handler;
- return old_handler;
- }
-
- failure_handler *error_mgr::restore_handler(void)
- {
- /* return to the previous error handler. */
- failure_handler *old_handler;
-
- setup();
- old_handler = curr_handler;
- curr_handler = (failure_handler *)
- (handler_stack->pop());
- if (curr_handler == 0)
- curr_handler = default_handler;
- return old_handler;
- }
-
- const char *error_mgr::message(
- const char *fmt,char *msg_line,
- int linelen)
- {
- /* search for the replacement text, writing */
- /* it into msg_line if found. return a pointer */
- /* to the start of the message, ready to print. */
- const char *new_fmt,*key,*keystart;
- char *key_alloc,*buf;
- int keylen;
-
- /* this routine calls setup() for fail(), */
-
- /* vfail(), etc. */
- setup();
- ASSERT(fmt != msg_line);
- msg_line[0] = '\0';
- if (*fmt ! = '$') { /* not keyed? */
- strncpy(msg_line,fmt,linelen);
- msg_line[linelen - 1] = '\0';
- return msg_line;
- }
-
- /* extract the key. */
- key = ++fmt; /* skip '$' */
- key += skipblanks(key);
- keylen = skip_ident(key); /* fetch key name */
- new_fmt = key + keylen; /* skip key name */
- new_fmt += skipblanks(key); /* skip to ':' */
-
- /* error text here is not replaceable - */
- /* recursive calls would be dangerous. */
- if (keylen == 0 ll *new_fmt != ':') {
- sprintf(msg_line,"Keyed error message format"
- "string is malformed\n%s",fmt);
- }
- else {
- key_alloc = newstring(key,keylen);
- ++new_fmt; /* skip ':' */
- new_fmt += skipblanks (new_fmt);
- if (find_replacement(key_alloc,msg_line,
- linelen)) {
- if (!proper_format(new_fmt,msg_line)) {
- /* invalid; dictionary text left at */
- /* front of resulting error message. */
- buf = msg_line + strlen(msg_line);
- sprintf(buf,"Replacement text for "
- "keyed error message is "
- "malformed\n%s", fmt);
- }
- }
- else { /* use program version */
- strncpy(msg_line,new_fmt,linelen);
- msg_line[linelen - 1] = '\0';
- }
- free(key_alloc);
- }
- return msg_line;
- }
-
- int error_mgr::find_replacement(
- const char *key,char *msg_line,
- int linelen)
- {
- /* search for message replacement. returns 1 */
- /* if found, 0 if not. */
- error_dict_list *curr_dict;
-
- for (curr_dict = error_dicts; curr_dict != 0;
- curr_dict = curr_dict->prev)
- if (curr_dict->dict->key_defined(key))
- break;
-
- return curr_dict != 0 &&
- curr_dict->dict->complaint_text(
- key,msg_line,linelen);
- }
-
- static int proper_format(const char *pgm_text,
- const char *user_text)
- {
- /* return 1 if the printf() format specifiers */
- /* in user_text match the ones in pgm_text. */
- char pgm_fmt,user_fmt;
-
- do {
- pgm_fmt = format_specifier(&pgm_text);
- user_fmt = format_specifier(&user_text);
- if (pgm_fmt!= user_fmt)
- return 0;
- } while (*pgm_text *user_text);
- return 1;
- }
-
- static char format_specifier(const char **fmt)
- {
- /* skip to the next '%' specifier (if any) */
- /* and return a unique letter indicating its */
- /* type. advances *fmt past the specifier. */
- const char *s : *fmt; /* *s vs. **fmt */
- char c;
-
- for (;;) { /* skip to '%' */
- while (*s && *s != '%')
- ++s;
- if (*s == '\0')
- break;
- ++s; /* skip '%' */
- if (*s == '%') /* "%%" prints '%' */
- ++s; /* not specifier */
- else
- break;
- }
- if (*s == '\0') {
- *fmt = s; /* update caller var */
- return '\0';
- }
-
- /* skip to the specifier letter. */
- while (*s && !isspace(*s) && !isalpha(*s))
- ++s;
- if (!isalpha(*s)) {
- *fmt = s; /* update caller var */
- return '\0'; /* bad specifier */
- }
-
- /* any 'l' is assumed to be long. */
- if (*s == 'l' && isalpha(*(s + 1))) {
- *fmt = s + 2; /* skip l, specifier */
- return '1';
- }
-
-
- /* map the specifier into a canonical value. */
- c = tolower(*s);
- *fmt = s + 1; /* update caller var */
- switch(c) {
- case 'e': case 'f': case 'g':
- return 'e'; /* floating point */
- case 'o': case 'u': case 'x':
- case 'd': case 'i': case 'b':
- return 'd'; /* int */
- default: /* all others map */
- return c; /* to themselves */
- } /* end of switch(c) */
- /* NOTREACHED */
- }
-
- void error_mgr::fail(const char *fmt,...)
- {
- /* print the message and exit. */
- va_list ap;
-
- va_start(ap,fmt);
- vfail(fmt,ap); /* won't return */
- /* NOTREACHED */
- va_end(ap);
- }
-
- void error_mgr::vfail(const char *fmt,va_list ap)
- {
- /* fail() with a va_list already built. if */
- /* the handler doesn't exit we force it. */
- fmt = message(fmt,line,sizeof(line));
- curr_handler->fail(fmt,ap);
- exit(EXIT_FAILURE); /* just in case! */
- }
-
- void error_mgr::error(const char *fmt,...)
- {
- /* print the message and ask for permission to */
- /* continue. */
- va_list ap;
-
- va_start(ap,fmt);
- verror(fmt,ap);
- va_end(ap);
- }
-
- void error_mgr::verror(const char *fmt,va_list ap)
- {
- /* error() with a va_list already built. */
- /* we replace the message if possible. */
- fmt = message(fmt,line,sizeof(line));
- curr_handler->error(fmt,ap);
- }
-
- int error_mgr::set_assert_flag(int asserts_on)
- {
- /* enable or disable assertions at run time. */
- int retval = asserts_off;
-
-
- /* set to opposite state because we want to */
- /* short-circuit assertion evaluation. */
- asserts_off = !asserts_on;
- return !retval;
- }
-
- int error_mgr::assert_failed(
- const char *exp,const char *fname,
- unsigned linenum)
- {
- /* an assertion has failed. */
- error("Assertion failed - file %s, line %u:\n%s\n",
- fname,linenum,exp);
- return 1; /* needed for macro */
- }
-
- // End of File
-
-
- Listing 7 utils.hpp - text and pointer utilities. utils.cpp is on the monthly
- source code disk
- #ifndef UTILS_HPP
- #define UTILS_HPP
-
- #include <stdio.h>
-
- int skipblanks(const char *s); /* returns length */
- int skip_ident(const char *s); /* returns length */
-
- /* allocates with malloc. copies only "len" chars */
- /* unless len == 0; then copies entire string */
-
- char *newstring(const char *s,int len = );
-
- /* read "logical" line, quoting newlines if the */
- /* character before them is '\\' */
-
- void read_continued_line(FILE *f,char *line,
- int linelen);
-
- /* non-template pointer stack. doesn't delete */
- /* anything when deleted. */
-
- class ptr_stack {
- public:
- ptr_stack(void);
- ptr_stack(const ptr_stack &other);
- ptr_stack &operator =(const ptr_stack &other);
- ~ptr_stack(void);
- void *push(void *op);
- long elem_count(void); /* 0L if empty */
- void *top(void) const; /* doesn't pop */
- void * pop(void); /* 0 if empty */
- private:
- long size, top_idx;
- void **elems;
- }; /* end of class ptr_stack */
-
- #endif /* UTILS_HPP */
- // End of File
-
-
-
- Listing 8 Definition of class complaint_dict
- /* complain.hpp - message dictionary handler */
-
- #ifndef COMPLAIN_HPP
- #define COMPLAIN_HPP
-
- class complain_ptr; /* in complain.cpp */
-
- class complaint_dict {
- public:
- complaint_dict(const char *filename);
- ~complaint_dict(void);
- int key_defined(const char *name) const;
- int complaint_text(const char *name,
- char *line,
- int linelen) const;
- const char *filename(void) const
- { return_filename; }
- private:
- char *_filename;
- complain_ptr *complain_table;
- /* unimplemented: */
- complaint_dict(const complaint_dict &other);
- complaint_dict &operator
- =(const complaint_dict &other);
- }; /* end of class complaint_dict */
-
- #endif /* COMPLAIN_HPP */
- // End of File
-
-
- Listing 9 Member functions of class complaint_dict
- /* complain.cpp - message dictionary manager */
-
- #include <stdio.h>
- #ifdef __ZTC______LINEEND____
- #include <io.h> /* fseek() */
- #endif
- #include <stdlib.h>
- #include <string.h>
- #include "utils.hpp"
- #include "errmgr.hpp"
- #include "complain.hpp"
-
- /* a singly linked list of keys - should use hash */
- /* table for speed */
- class complain_ptr {
- public:
- complain_ptr(char *name,long offset,
- complain_ptr *head);
- ~complain_ptr(void);
- const char *errname(void) const
- { return_errname; }
- int complaint_text(const char *filename,
- char *line,int linelen);
- complain_ptr *next;
- private:
-
- char * _errname;
- long foffset;
- };
-
- /************** class complaint_dict ***************/
-
- static complain_ptr *read_complaint_file(
- const char *filename);
-
- complaint_dict::complaint_dict(const char *filename)
- {
- /* constructor - remember file name, read all */
- /* messages and store offsets */
- _filename= newstring(filename);
- complain_table = read_complaint_file(_filename);
- }
-
- complaint_dict: :~complaint_dict (void)
- {
- /* cleanup - delete all of the offset records */
- /* in the table. */
- complain_ptr *keyptr;
-
- free(_filename);
- while ((keyptr= complain_table) != 0) {
- complain_table = complain_table->next;
- delete keyptr;
- }
- }
-
- static complain_ptr *key(complain_ptr *complain_table,
- const char *name)
- {
- /* look for the key in the list of complain_ptr */
- /* objects. returns 0 if not found. */
- while (complain_table != 0 &&
- strcmp(name,complain_table->errname()))
- complain_table= complain_table->next;
- return complain_table;
- }
-
- int complaint_dict::key_defined(const char *name) const
- {
- /* return 1 if the key is defined here. */
- return key(complain_table,name) != 0;
- }
-
- int complaint_dict::complaint_text(const char *name,
- char *line,
- int linelen) const
- {
- /* retrieve the text for the named message and */
- /* store it in the buffer. returns 0 on failure. */
- complain_ptr *fmt;
-
- fmt = key(complain_table,name);
- if (fmt == 0) {
- sprintf(line,"Error key \"%s\" not found\n",
- key);
-
- return 0; /* failed */
- }
- if (fmt->complaint_text(_filename,line,linelen)) {
- sprintf(line,"Unable to re-read text for"
- "error key \"%s\"\n",key);
- return 0; /* failed */
- }
-
- return 1; /* all OK */
-
- } /* end of complaint_dict::complaint_text() */
-
- /************* local utility routines **************/
-
- static complain_ptr *read_complaint_file(
- const char *filename)
- {
- /* read all of the error keys in the complaint */
- /* file. */
- FILE *complaintfile;
- char line[512];
- long thisoffset;
- char *keyname,*p,*name;
- int all_ok = 1;
- complain_ptr *keyptr,*complain_table = 0;
-
- if ((complaintfile = fopen(filename,"r")) == 0) {
- err_mgr.error("Unable to read error text"
- "file %s\n",filename);
- return 0; /* OK to continue */
- }
-
- for (;;) { /* exit from within */
- /* remember the start of each line. */
- thisoffset = ftell(complaintfile);
- read_continued_line(complaintfile,line,
- sizeof(line));
- if (strlen(line) == 0) /* EOF? done*/
-
- break;
-
- /* skip blank lines and comments. */
- keyname = p = line + skipblanks(line);
- if (!*p *p == '\n' *P == '#')
- continue;
- p += skip_ident(p); /* get key name */
- if (p == keyname) {
- err_mgr.warn("Missing error key in error"
- text file \"%s\":\n%s",
- filename,line);
- all_ok = 0;
- continue;
- }
-
- name = newstring(keyname,p - keyname);
- if (key(complain_table,name) != 0) {
- err_mgr.warn("Duplicate error key in"
- "error text file \"%s\":\n%s",
- filename,line);
-
- free(name);
- all_ok = 0;
- continue;
- }
- p += skipblanks(p);
- if (*p != ':') {
- err_mgr.warn("Missing ':' in error text"
- "file \"%s\":\n%s",
- filename,line);
- free(name);
-
- all_ok = 0;
- continue;
- }
-
- /* everything looks good - remember key. */
- keyptr = new complain_ptr(name,thisoffset,
- complain_table);
- complain_table = keyptr;
- }
-
- /* if problems were found ignore entire file. */
- fclose(complaintfile);
- if (!all_ok) {
- while ((keyptr = complain_table) != 0) {
- complain table = complain_table->next;
- delete keyptr;
- }
- return 0;
- }
- return complain_table;
- }
-
- /**************** class complain_ptr ****************/
-
- complain_ptr:: complain_ptr(char *name, long offset,
- complain_ptr *head)
- { /* constructor */
- /* add the new complain_ptr to the front of */
- /* the list. name is allocated for us; the */
- /* destructor must free it. */
- _errname = name;
- foffset = offset;
- next = head;
- }
-
- complain_ptr::~complain_ptr(void) /* destructor */
- {
- /* our caller cleans up the chain in next. */
- free(_errname);
- }
-
- int complain_ptr::complaint text(
- const char *filename,
- char *line,int linelen)
- {
- /* re-read the message text for the key. */
- /* returns 1 on error (file has disappeared or */
- /* been edited). */
-
- FILE *complaintfile;
- char *keyname,*p;
- int newlen;
-
- line[0] = '\0'; /* clear old text */
- if ((complaintfile = fopen(filename,"r")) == 0)
- return 1;
- if (fseek(complaintfile,foffset,SEEK_SET)) {
- fclose(complaintfile);
- return 1;
- }
- read_continued_line(complaintfile,line,linelen);
- fclose(complaintfile);
-
- /* make sure we still have the same key! (file */
- /* may have been edited) */
- keyname = p = line + skipblanks(line);
- p += skip_ident(p);
- if (p == keyname
- strncmp(keyname, errname,strlen(_errname)))
- return 1;
- p += skipblanks(p);
-
- if (*p != ':')
- return 1;
- ++p; /* skip ':', */
- p += skipblanks(p); /* leading blanks */
-
- /* shift everything over to cover the key. */
- newlen = strlen(p);
- memmove(line,p,newlen + 1);
- return 0; /* all OK */
- }
- // End of File
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Designing a Cross-Platform GUI
-
-
- Laszlo Zeke
-
-
- Laszlo Zeke has been supervising, designing, and developing GUI-based
- applications over 12 years. He has an M.A. in mathematics. He resides in
- Herndon, Virginia, and is the Director of Kernel Product Engineering for
- INCODE Corporation. Laszlo can be reached via e-mail at incode@netcom.com or
- via CompuServe at 76667,1776.
-
-
-
-
- Introduction
-
-
- Sooner or later you're bound to meet a customer who likes your GUI-based
- application but wants it to run in his own GUI environment, different from
- yours. You may wish to accomodate customers such as this, but find it too
- expensive to maintain different code sets on different platforms.
- A common solution is to package the GUI-dependent pieces into an object
- library; the application will then use the objects implemented in this GUI
- library only, instead of the native GUI interfaces.
- You have two basic choices in implementing this type of object library: either
- purchase a third-party multi-platform toolkit (there are plenty), or develop
- your own, using the native APIs. Both approaches have their merits, as well as
- proponents.
- In this article I demonstrate the latter approach by implementing a GUI
- library using the native APIs of Windows/NT, OS/2, and X-Windows. This is just
- a sample library since the source code of a full-blown, commercial-strength
- library would run well over 10,000 lines -- obviously beyond the space
- available here.
- This article presents the class design for the platform-independent portion of
- the GUI, plus a sample application. I also briefly describe the
- platform-specific parts of the code. The platform-dependent code is included
- on this month's code disk, and is available from the on-line sources listed on
- page 3.
-
-
- Overall Library Features
-
-
- An application that uses this library will have the following capabilities:
- It can have a number of windows.
- Each window may have a different background color and frame decorations.
- Each window can have a number of "children:" static text elements, buttons,
- entry fields, lines, and ellipses.
- If a window is resized, its children are automatically resized.
- The activation of every button and entry field causes the execution of an
- application-defined function.
-
-
- Library Design
-
-
- Ideally, I might have based my design solely on its ability to meet a given
- set of criteria, such as the features presented above. In reality, any design
- will be influenced by the tools at hand, in this case, the compilers available
- for the various platforms.
-
-
- Tools
-
-
- I selected the following tools for these three target environments:
- Windows NT/Window 3.1: Visual C++, plus Win32s 1.2 (Win32s 1.2 is required to
- run applications built with this library under Windows 3.1)
- OS/2: Borland C++ 1.5
- AIX: Motif 1.2, X11R5: XL C++, the C++ compiler for the AIX/RISC-6000 machines
- (Note: I am not suggesting that these tools are better than others; they are
- simply the tools I'm familiar with.)
- The code presented in this article will compile and run on each of these
- platforms. Since error checking would double the size of the code, I have left
- it out entirely.
-
-
- The General Application Framework
-
-
- The library tries to make use of the common elements in the native toolkits.
- When there is nothing in common, the library tries to bridge the differences
- with the least possible effort.
- Although each toolkit is different, any application that uses such a toolkit
- will follow the same general pattern, or framework:
- 1. some initialization of the toolkit
- 2. creation of one or more windows along with their children
- 3. processing messages in a loop that retrieves and dispatches different
- messages to the different windows and to their children based upon the user's
- actions
-
- 4. termination with some possible cleanup
- To encapsulate this initialize-message loop-cleanup model, I introduce a
- class, GUI_APPLICATION (See Listing 1, a GUI-independent header file for the
- library). The application will create an object of this class; this object
- should be created first, and destroyed last in any application.
- The main entities an application creates are windows. These are implemented in
- the class GUI_WINDOW. A window in this context has a frame, with some possible
- decorations (title, maximize button, sizable-border, etc.), and usually has
- one or more children. This library implements the child types static text,
- button, entry field, line, and ellipse. The corresponding classes are
- GUI_TEXT, GUI_BUTTON, GUI_ENTRY, GUI_LINE, and GUI_ELLIPSE.
- These classes naturally fall into two groups: those implemented in the native
- toolkit as special native windows, and those that must be created by some
- drawing APIs. The two groups descend from two different base classes, since
- they behave differently in many respects. (For instance, most of the native
- windows are repainted automatically as needed, the other type are not.)
- The two base classes are GUI_WINDOW_OBJECT and GUI_GRAPHICS_OBJECT.
- GUI_WINDOW_OBJECT is the common base class for GUI_TEXT, GUI_BUTTON,
- GUI_ENTRY, and GUI_WINDOW, whose objects are native windows.
- GUI_GRAPHICS_OBJECT is the common base class for GUI_LINE and GUI_ELLIPSE,
- whose objects must be drawn explicitly by the application.
- Though they behave differently, these two base classes share some common
- elements. For instance, all of them have x and y coordinates, they have width
- and height, etc. To extract these and some other common characteristics, I
- introduce a common base class, GUI_OBJECT. Figure 1 shows the class heirarchy
- just described.
-
-
- Processing User Input
-
-
- Most of the objects mentioned above must be able to accept user input and
- execute some functions based upon it. For instance, the button objects must
- accept a LEFT MOUSE BUTTON SINGLE CLICK event and execute an
- application-defined function. There are two ways to accomplish this.
- The first way is to store event-handler function pointers in the
- above-mentioned objects. The application will call the desired function
- through one of these pointers when a given event takes place. The other way to
- provide event responses is to create some virtual functions in the base class,
- to be overridden by an application-derived class. I chose the second approach,
- since it seems more modular and cleaner to me.
- I provide a default LeftButtonSingleClick function for every class, which
- function does nothing. Thus, a mouse click on a button of type GUI_BUTTON
- causes no harm, but not much excitement either. If you derive a class
- MY_BUTTON from GUI_BUTTON, and override LeftButtonSingleClick, then this new
- overridden function will be called whenever a MY_BUTTON is clicked. I do not
- use C++'s virtual function call mechanism to determine which button was
- pushed. To do so would require a new derived button class for practically
- every button in the program. Rather, I find out which button was activated by
- introducing a numeric ID field for every GUI_OBJECT, enabling a switch
- statement to be based upon this ID.
- These user input functions could be implemented in the GUI_OBJECT class, but I
- decided to implement them in a base class for GUI_OBJECT, CALLABLE_OBJECT. (If
- my class hierarchy were to grow, a new object class may not necessarily be
- derived from GUI_OBJECT, but I still might want to be able to process user
- input for that class.)
-
-
- Display Features
-
-
- My library assumes a window-relative MAX_X by MAX_Y coordinate system, with
- the upper left corner as (0,0). In such a coordinate system, a window with a
- width of MAX_X spans the whole width of the screen, while a button with a
- width of MAX_X spans the whole width of its parent window. The particular
- values for MAX_X and for MAX_Y are not really important but they should be
- finer than the actual display resolution. This library uses 10,000 for both
- MAX_X and MAX_Y, which is a safe choice for most contemporary displays.
- Matching appropriate colors among the different GUI environment is a subject
- worth a book in itself. Therefore, I chose a very simple approach and
- implemented the 16 VGA colors. Font handling is another issue that naturally
- arises, but its complexity is beyond the scope of this article. (However, a
- subset of available fonts could be easily managed in all of the mentioned
- environments.)
-
-
- Description of Classes
-
-
- In this section I describe the platform-independent portion of the class
- library. Figure 1 shows the class hierarchy. The top three layers of the
- hierarchy consist of abstract classes. The CALLABLE_OBJECT class declares
- several functions for responding to user input, but these are all virtual
- functions, hence they must be overridden and "filled in" in descendant
- classes. The next lower class, GUI_OBJECT, introduces a data member id, which
- stores an ID number for each object. Every GUI_OBJECT must have a unique ID. I
- also briefly describe class GUI_APPLICATION here, though it is not a member of
- the hierarchy.
-
-
- GUI_APPLICATION
-
-
- The GUI_APPLICATION class has no descendants. It serves as a wrapper around
- the application and maintains the main message loop. A GUI_APPLICATION object
- has four basic functions: a constructor to initialize the GUI toolkit, a
- destructor to clean up, a MainLoop function to handle the main message loop,
- and a Terminate function which breaks the message loop. This class maintains a
- single-linked list of GUI_WINDOW objects, the windows of the application.
-
-
- GUI_WINDOW_OBJECT
-
-
- GUI_WINDOW_OBJECT serves as an abstract base class for GUI_WINDOW. A
- GUI_WINDOW object maintains a handle to its own native window, in protected
- member window of GUI_WINDOW_OBJECT. Each GUI_WINDOW object also maintains a
- single-linked list of GUI_OBJECT objects comprising its children, such as
- buttons, text elements, lines, etc. (Note that these "children" are not
- descendants of GUI_WINDOW; rather, they are children in the sense that they
- are owned by a GUI_WINDOW object.) When a GUI_WINDOW object is created it does
- not appear immediately, therefore the application has a chance to create its
- children first. Calling GUI_WINDOW's Show function finally makes it appear.
- GUI_WINDOW also has a function Message to put up a message box, and a function
- Question to ask the user a yes/no question. GUI_WINDOW's GetText/SetText
- function pair enables an application to dynamically get/set the title of its
- window.
- GUI_TEXT, GUI_BUTTON, and GUI_ENTRY are descendants of GUI_WINDOW_OBJECT whose
- objects function as children of GUI_WINDOW objects. A GUI_TEXT object contains
- a non-modifiable piece of text to be displayed in the window. The user cannot
- change its contents. A GUI_BUTTON object represents a pushbutton. A GUI_ENTRY
- object represents a single-line, specified-text-length, autoscrolling entry
- field. These are the only two classes in this library that override the
- user-input functions declared in CALLABLE_OBJECT.
- Specifically, a GUI_BUTTON object calls the following functions:
- Activate. The button object calls this function if the user activates the
- button, either through the mouse or through the keyboard
- LeftClick. Called when the GUI_BUTTON is clicked with the left mouse button
- LeftDoubleClick. Called when the GUI_BUTTON is double-clicked with the left
- mouse button
- RightClick. Called when the GUI_BUTTON is clicked with the right mouse button
- RightDoubleClick. Called when the GUI_BUTTON is double-clicked with the right
- mouse button
- A GUI_ENTRY object calls the Activate function if the entry field is losing
- its focus.
- Supplying a height parameter of zero to a GUI_TEXT, GUI_BUTTON, or GUI_ENTRY
- object will cause it to be created with the default font height. It is also
- possible to dynamically get/set these objects' text via the GetText/SetText
- function pair.
-
-
- GUI_GRAPHICS_OBJECT
-
-
- A GUI_GRAPHICS_OBJECT is an abstract base class for objects that can't be
- represented as native windows. It has two descendants, GUI_ELLIPSE and
- GUI_LINE. A GUI_ELLIPSE object represents a filled/outlined color-specified
- ellipse. A GUI_LINE object represents a color-specified straight line.
-
-
- Class Implementations
-
-
-
- I've divided the source for this class library (about 750 lines on every
- platform) into two parts: gui.c (Listing 2) contains functions that don't use
- native APIs; native.c, provided separately for each platform, contains
- functions that do use native APIs. Due to space limitations, the three
- native.c files are not shown here, but are available on this month's code disk
- and CUJ online sources (see p. 3 for more information on online sources).
- At a structural level, each native implementation shares some common features.
- For example, each implementation must perform some sort of registration of a
- window or application with the operating system. Each implementation must
- interact with a main message loop and respond to messages from the system.
- Finally, each implementation must explicitly draw graphic objects of type
- GUI_LINE and GUI_ELLIPSE, rather than rely upon the native system to do the
- work.
- The greatest difference in implementations occurs between the Windows
- look-alikes (Windows 3.1, Windows NT, and OS/2) and X-Window. For example,
- X-Window requires an application to register callback functions for painting
- and resizing windows, while the look-alikes do this in response to system
- messages (e.g. WM_PAINT). Another significant difference appears in the way
- the two implementations manage to catch all mouse events for nongraphical
- windows. The Windows look-alikes do so by "subclassing the window," overriding
- the built-in window procedure for buttons. The X implementation catches such
- events by registering an event handler with the system.
- More details, as well as documentation, are provided on this month's code
- disk.
-
-
- A Short Demo
-
-
- The application presented here (Listing 3), while not extremely "useful,"
- demonstrates how easy it is to build an application using the library. Figure
- 2 is a screen capture of the demo running on AIX/X-Windows. The application
- window's Exit button terminates the application, while its Next button creates
- an identical window, shifted a little to the upper left and displayed in a
- different color. The application window's entry field determines the title of
- the window. Both of the window's buttons react to right-mouse-button clicks
- with popup messages.
-
-
- Conclusion
-
-
- Constructing a multi-platform GUI is not a simple task. Many problems will
- naturally arise that this article does not address: drawing optimization;
- creation of menu bars, accelerator keys, and popup menus; implementation of
- drag and drop operations, and z-order for children of a window; font
- management and national language support, just to mention a few. Some of these
- features can be added with relative ease (menus, optimized drawing, z-order),
- while others may require considerable effort (font management, drag and drop).
- However, if you're the kind of programmer who likes to experiment, or "roll
- your own," this class library should give you a good place to start.
-
-
- Information Sources
-
-
- Borland C++ for 0S/2 Ver 1.5, on CD-ROM. Produced by Borland International,
- 1994.
- Microsoft Development Platform, on CD-ROM. Produced by Microsoft Corp., 1995.
- O'Reilly, Tim, ed. X series, Release 4 and Release 5, vols. 0 - 8. O'Really &
- Associates, 1990-1994.
- Figure 1 The Library Class Hierarchy
- Figure 2 Demo on AIX/X-Window
-
- Listing 1 gui.h -- Platform-independent header file for class library
- gui.h
-
- #ifndef _GUI_____LINEEND____
- #define _GUI_____LINEEND____
-
- #include <stdarg.h>
-
- // platform specific includes and defines
-
- #ifdef WIN_NT
- #include <windows.h>
-
- typedef HWND NativeWindow;
- typedef HDC GraphicsHandle;
- #define EXP1
- #define EXP2 _declspec(dllexport)
- #define NATIVE_PART_OF_GUI_APPLICATION HINSTANCE hInstance
- #define NATIVE_PART_OF_GUI_WINDOW
- #define NATIVE_FRIEND_OF_GUI_WINDOW friend LRESULT CALLBACK \
- GuiWindowProc(HWND hWnd, UINT message, \
- WPARAM uParam, LPARAM lParam)
- #define main ApplicationMain
- #endif
-
- #ifdef OS2
- #define INCL_WIN
- #define INCL_GPI
- #include <os2.h>
-
- typedef HWND NativeWindow;
-
- typedef HPS GraphicsHandle;
- #define EXP1 _export
- #define EXP2
- #define NATIVE_PART_OF_GUI_APPLICATION HAB Hab; \
- HMQ Hmq
- #define NATIVE_PART_OF_GUI_WINDOW NativeWindow frame
- #define NATIVE_FRIEND_OF_GUI_WINDOW friend MRESULT \
- EXPENTRY GuiWindowProc(HWND hwnd, USHORT msg, \
- MPARAM mp1, MPARAM mp2)
- #endif
-
- #ifdef X_WINDOWS
- #include <X11/Intrinsic.h>
-
- typedef Widget NativeWindow;
- typedef GC GraphicsHandle;
- #define EXP1
- #define EXP2
- #define NATIVE_PART_OF_GUI_APPLICATION int dontQuit
- #define NATIVE_PART_OF_GUI_WINDOW NativeWindow frame; \
- char szGeometry[100]
- #define NATIVE_FRIEND_OF_GUI_WINDOW \
- friend void ExposeCallback(Widget w, \
- XtPointer client_data, \
- XtPointer call_data); \
- friend void ResizeCallback(Widget w, \
- XtPointer client_data, \
- XtPointer call_data)
- #endif
-
- // coordinate system definition
-
- #define MAX_X 10000
- #define MAX_Y 10000
-
- #define MAX_WINDOW 100 // max. windows in an application
- #define MAX_CHILDREN 100 // max. children of a window
-
- // VGA colors
-
- #define COLOR_WHITE 0
- #define COLOR_BLACK 1
- #define COLOR_BLUE 2
- #define COLOR_RED 3
- #define COLOR_PINK 4
- #define COLOR_GREEN 5
- #define COLOR_CYAN 6
- #define COLOR_YELLOW 7
- #define COLOR_NEUTRAL 8
- #define COLOR_DARKGRAY 9
- #define COLOR_DARKBLUE 10
- #define COLOR_DARKRED 11
- #define COLOR_DARKPINK 12
- #define COL0R_DARKGREEN 13
- #define COLOR_DARKCYAN 14
- #define COLOR_BROWN 15
-
- // object types returned by GetType()
-
-
- #define TYPE_LINE 1
- #define TYPE_ELLIPSE 2
- #define TYPE_TEXT 3
- #define TYPE_BUTTON 4
- #define TYPE_ENTRY 5
- #define TYPE_WINDOW 6
-
- // the only global function: logs into a file
- EXP2 void EXP1 Log(char* fmt, ...);
-
- // forward declaration of classes GUI_WINDOW and GUI_OBJECT
- class GUI_WINDOW;
- class GUI_OBJECT;
-
- typedef struct _GUI_WINDOW_ELEM { // typedef for linked list of windows
- GUI_WINDOW* pWindow;
- struct _GUI_WINDOW_ELEM* pNext;
- } GUI_WINDOW_ELEM;
-
- class EXP1 GUI_APPLICATION {
- private:
- NATIVE_PART_OF_GUI_APPLICATION;
-
- GUI_WINDOW_ELEM WindowElemList[MAX_WINDOW], *pUsed, *pFree;
- int AddWindow(GUI_WINDOW *pWindow);
- int DelWindow(GUI_WINDOW *pWindow);
- public:
- EXP2 GUI_APPLICATION(int* pArgc, char** argv, char* logFile);
- EXP2 virtual ~GUI_APPLICATION(void);
- EXP2 void MainLoop(void);
- EXP2 void Terminate(void);
- EXP2 GUI_WINDOW* FindWindow(NativeWindow window);
- EXP2 GUI_OBJECT* FindChild(NativeWindow window);
-
- friend class GUI_WINDOW;
- };
-
- class EXP1 CALLABLE_OBJECT {
- public:
- EXP2 virtual int Activate(void) { return(1); }
- EXP2 virtual int LeftClick(void) { return(1); }
- EXP2 virtual int RightClick(void) { return(1); }
- EXP2 virtual int LeftDoubleClick(void) { return(1); }
- EXP2 virtual int RightDoubleClick(void) { return(1); }
- };
-
- class EXP1 GUI_OBJECT: public CALLABLE_OBJECT { // abstract class
- protected:
- GUI_WINDOW* pParent;
- int id, x, y, width, height;
- public:
- EXP2 GUI_OBJECT(GUI_WINDOW* pParent, int id, int x=0, int y=0,
- int width=MAX_X, int height=MAX_Y);
- EXP2 virtual ~GUI_OBJECT(void);
- EXP2 virtual int GetType(void)=0;
- EXP2 virtual void Paint(GraphicsHandle gh) { }
- EXP2 virtual NativeWindow GetNativeWindow(void) { return(0); }
- EXP2 GUI_WINDOW* GetParent(void) { return(pParent); }
- EXP2 int GetId(void) { return(id); }
-
- EXP2 int GetX(void) { return(x); }
- EXP2 int GetY(void) { return(y); }
- EXP2 int GetWidth(void) { return(width); }
- EXP2 int GetHeight(void) { return(height); }
- };
-
- class EXP1 GUI_GRAPHICS_OBJECT: public GUI_OBJECT { // abstract class
- protected:
- int color, lineWidth;
- public:
- EXP2 GUI_GRAPHICS_OBJECT(GUI_WINDOW* pParent, int id, int x=0,
- int y=0,int width=MAX_X, int height=MAX_Y,
- int color=COLOR_BLACK, int lineWidth=1);
- EXP2 virtual ~GUI_GRAPHICS_OBJECT(void);
- };
-
- class EXP1 GUI_LINE: public GUI_GRAPHICS_OBJECT {
- public:
- EXP2 GUI_LINE(GUI_WINDOW* pParent, int id, int x1=0, int y1=0,
- int x2=MAX_X, int y2=MAX_Y,
- int color=COLOR_BLACK, int lineWidth=1);
- EXP2 virtual ~GUI_LINE(void);
- EXP2 virtual int GetType(void) { return(TYPE_LINE); }
- EXP2 virtual void Paint(GraphicsHandle gh);
- };
-
- // fill types
-
- #define FILL_OUTER 0 // draw just the otline
- #define FILL_SOLID 1 // draw and fill the shape
-
- class EXP1 GUI_ELLIPSE: public GUI_GRAPHICS_OBJECT { // abstract class
- protected:
- int fillType;
- public:
- EXP2 GUI_ELLIPSE(GUI_WINDOW* pParent, int id, int x=0, int y=0,
- int width=MAX_X/2, int height=MAX_Y/2,
- int color=COLOR_BLACK, int fillType=FILL_OUTER,
- int lineWidth=1);
- EXP2 virtual ~GUI_ELLIPSE(void);
- EXP2 virtual int GetType(void) { return(TYPE_ELLIPSE); }
- EXP2 virtual void Paint(GraphicsHandle gh);
- };
-
- class EXP1 GUI_WINDOW_OBJECT: public GUI_OBJECT { // abstract class
- protected:
- NativeWindow window;
- public:
- EXP2 GUI_WINDOW_OBJECT(GUI_WINDOW* pParent, int id, int x=0,
- int y=0, int width=MAX_X, int height=MAX_Y);
- EXP2 virtual ~GUI_WINDOW_OBJECT(void);
- EXP2 NativeWindow GetNativeWindow(void) { return(window); }
- EXP2 virtual void GetRealCoordinates(int *pRx, int *pRy,
- int *pRw, int *pRh);
- EXP2 virtual int GetText(char *buffer, int bufferLength);
- EXP2 virtual int SetText(char *text="");
- };
-
- class EXP1 GUI_TEXT: public GUI_WINDOW_OBJECT {
-
- public:
- EXP2 GUI_TEXT(GUI_WINDOW* pParent, int id, int x=0, int y=0,
- int width=MAX_X, int height=0,
- char *text="");
- EXP2 virtual int GetType(void) { return(TYPE_TEXT); }
- };
-
- class EXP1 GUI_BUTTON: public GUI_WINDOW_OBJECT {
- public:
- EXP2 GUI_BUTTON(GUI_WINDOW* pParent, int id, int x=0, int y=0,
- int width=MAX_X, int height=0,
- char* text="");
- EXP2 virtual int GetType(void) { return(TYPE_BUTTON; }
- };
-
- class EXP1 GUI_ENTRY: public GUI_WINDOW_OBJECT {
- protected:
- int length;
- public:
- EXP2 GUI_ENTRY(GUI_WINDOW* pParent, int id, int x=0, int y=0,
- int width=MAX_X, int height=0,
- char* text="", int maxTextLength=10);
- EXP2 virtual int GetType(void) { return(TYPE_ENTRY); }
- #ifdef X_WINDOWS // if not under X_WINDOWS these are inherited from
- // GUI_WINDOW_OBJECT
- EXP2 virtual int GetText(char *buffer, int bufferLength);
- EXP2 virtual int SetText(char *text="");
- #endif
- };
-
- // frame styles
-
- #define FRAME_THIN 1
- #define FRAME_SIZEABLE 2
- #define FRAME_TITLE 4
- #define FRAME_MIN_BUTTON 8
- #define FRAME_MAX_BUTTON 16
- #define FRAME_DEFAULT (FRAME_THIN FRAME_TITLE)
-
- typedef struct _GUI_OBJECT_ELEM { // typedef for linked list of children
- GUI_OBJECT* pObject;
- struct _GUI_OBJECT_ELEM* pNext;
- } GUI_OBJECT_ELEM;
-
- class EXP1 GUI_WINDOW: public GUI_WINDOW_OBJECT {
- private:
- NATIVE_PART_OF_GUI_WINDOW;
-
- GUI_APPLICATION* pApplication;
- GraphicsHandle gh;
- int frameStyle, backgroundColor;
- GUI_OBJECT_ELEM Children[MAX_CHILDREN], *pUsed, *pFree;
- protected:
- EXP2 void SizeChildren(void);
- EXP2 int AddChild(GUI_OBJECT* pChild);
- EXP2 int DeleteChild(GUI_OBJECT* pChild);
- public:
- EXP2 GUI_WINDOW(GUI_APPLICATION* pApplication, int id, int x=0,
- int y=0, int width=MAX_X, int height=MAX_Y,
-
- int backgroundColor=COLOR_WHITE, char* title= "",
- int frameStyle=FRAME_DEFAULT);
- EXP2 virtual ~GUI_WINDOW(void);
- EXP2 virtual int GetType(void) { return(TYPE_WINDOW); }
- EXP2 virtual void Paint(GraphicsHandle gh);
- EXP2 virtual void Show(void);
- EXP2 int GetBackground(void) {return(backgroundColor); }
- EXP2 GraphicsHandle GetGraphicsHandle(void) { return(gh); }
- EXP2 GUI_OBJECT* GetChildFromId(int id);
- EXP2 GUI_OBJECT* GetChildFromWindow(NativeWindow window);
- #ifndef WIN_NT // if under Windows these are inherited from GUI_WINDOW_OBJECT
- EXP2 virtual int GetText(char *buffer, int bufferLength);
- EXP2 virtual int SetText(char *text="");
- #endif
- EXP2 virtual void Message(char *title, char *text);
- // return value 0=OK, 1=Cancel
- EXP2 virtual int Question(char *title, char *text);
-
- friend class GUI_APPLICATION;
- friend class GUI_OBJECT;
- NATIVE_FRIEND_OF_GUI_WINDOW;
- };
-
- #endif // #ifndef _GUI_____LINEEND____
-
- /* End of File */
-
-
- Listing 2 gui.c -- Implementation of platform-independent functions
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
-
- #include "gui.h"
-
- extern int charHeight;
- extern FILE* fErrLog;
- extern GUI_APPLICATION* pGuiApplication;
-
- //*** logging function into a file opened by GUI_APPLICATION
-
- void Log(char* fmt, ...)
- {
- va_list ap;
-
- if(fErrLog) {
- va_start(ap, fmt);
- vfprintf(fErrLog, fmt, ap);
- fflush(fErrLog); // to protect against losing log if crashing
- va_end(ap);
- }
- }
-
- //*** Functions for GUI_APPLICATION
-
- int GUI_APPLICATION::AddWindow(GUI_WINDOW *pWindow)
- {
- int ret = 1;
- GUI_WINDOW_ELEM* pTmp;
-
-
- // add a GUI_WINDOW to the linked list of windows
- if(pFree) {
- pTmp = pFree;
- pFree = pFree->pNext;
- pTmp->pWindow = pWindow;
- pTmp->pNext = pUsed;
- pUsed = pTmp;
- ret = 0;
- }
-
- if(ret) Log("Error: too many windows.\n");
-
- return(ret);
- }
-
- int GUI_APPLICATION::DelWindow(GUI_WINDOW* pWindow)
- {
- int ret = 1;
- GUI_WINDOW_ELEM *pTmp, *pPrev;
-
- // delete a window from the linked lists of windows
- for(pTmp = pUsed, pPrev = NULL; pTmp; pPrev = pTmp,
- pTmp = pTmp->pNext) {
- if(pTmp->pWindow == pWindow) {
- // take out of used list
- if(pPrev) pPrev->pNext = pTmp->pNext;
- else pUsed = pTmp->pNext;
- // put into free list
- pTmp->pNext = pFree;
- pFree = pTmp;
- ret = 0;
- break;
- }
- }
-
- if(ret) Log("Error: can't delete window %p\n", pWindow);
-
- return(ret);
- }
-
- GUI_WINDOW* GUI_APPLICATION::FindWindow(NativeWindow window)
- {
- GUI_WINDOW_ELEM *pTmp;
-
- // find a window in its list of windows
- for(pTmp = pUsed; pTmp; pTmp = pTmp->pNext) {
- if(pTmp->pWindow->GetNativeWindow() == window) break;
- }
-
- return(pTmp ? pTmp->pWindow : NULL);
- }
-
- //*** Functions for GUI_OBJECT
-
- GUI_OBJECT::GUI_OBJECT(GUI_WINDOW* pParent, int id, int x, int y,
- int width, int height)
- : id(id), pParent(pParent), x(x), y(y),
- width(width), height(height)
-
- {
- // add this object to its parent children list
- if(pParent) pParent->AddChild(this);
- }
-
- GUI_OBJECT::~GUI_OBJECT(void)
- {
- // delete this object from its parent children list
- if(pParent) pParent->DeleteChild(this);
- }
-
- //*** Functions for GUI_GRAPHICS_OBJECT
-
- GUI_GRAPHICS_OBJECT::GUI_GRAPHICS_OBJECT(GUI_WINDOW* pParent, int id,
- int x, int y,
- int width, int height,
- int color, int lineWidth)
- :GUI_OBJECT(pParent, id, x, y, width, height),
- color(color), lineWidth(lineWidth)
- {
- // nothing to do
- }
-
- GUI_GRAPHICS_OBJECT::~GUI_GRAPHICS_OBJECT(void)
- {
- // nothing to do
- }
-
- //*** Functions for GUI_LINE
-
- GUI_LINE::GUI_LINE(GUI_WINDOW* pParent, int id, int x1, int y1, int x2,
- int y2, int color, int lineWidth)
- :GUI_GRAPHICS_OBJECT(pParent, id, x1, y1, x2, y2, color, lineWidth)
- {
- // paint the first time around
- Paint(pParent->GetGraphicsHandle());
- }
-
- //*** Functions for GUI_ELLIPSE
-
- GUI_ELLIPSE::GUI_ELLIPSE(GUI_WINDOW* pParent, int id, int x, int y,
- int width, int height,
- int color, int fillType, int lineWidth)
- :GUI_GRAPHICS_OBJECT(pParent, id, x, y, width, height,
- color, lineWidth),
- fillType(fillType)
- {
- // paint the first time around
- Paint(pParent->GetGraphicsHandle());
- }
-
- //*** Functions for GUI_WINDOW_OBJECT
-
- GUI_WINDOW_OBJECT::GUI_WINDOW_OBJECT(GUI_WINDOW* pParent, int id,
- int x, int y, int width, int height)
- :GUI_OBJECT(pParent, id, x, y, width, height)
- {
- window = 0;
- }
-
-
- //*** Functions for GUI_WINDOW
-
- int GUI_WINDOW::AddChild(GUI_OBJECT* pChild)
- {
- int ret = 1;
- GUI_OBJECT_ELEM* pTmp;
-
- // add pChild to the linked list of children
- if(pFree) {
- pTmp = pFree;
- pFree = pFree->pNext;
- pTmp->pObject = pChild;
- pTmp->pNext = pUsed;
- pUsed = pTmp;
- ret = 0;
- }
-
- if(ret) Log("Error: too many children.\n");
-
- return(ret);
- }
-
- int GUI_WINDOW::DeleteChild(GUI_OBJECT* pChild)
- {
- int ret = 1;
- GUI_OBJECT_ELEM *pTmp, *pPrev;
-
- // delete pChild from the linked list of children
- for(pTmp = pUsed, pPrev = NULL; pTmp; pPrev = pTmp,
- pTmp = pTmp->pNext) {
- if(pTmp->pObject == pChild) {
- // take out of used list
- if(pPrev) pPrev->pNext = pTmp->pNext;
- else pUsed = pTmp->pNext;
- // put into free list
- pTmp->pNext = pFree;
- pFree = pTmp;
- ret = 0;
- break;
- }
- }
-
- if(ret) Log("Error: can't delete child %p\n", pChild);
-
- return(ret);
- // get child pointer from its id; it returns NULL if fails
-
- GUI_OBJECT* GUI_WINDOW::GetChildFromId(int id)
- {
- GUI_OBJECT_ELEM *pTmp;
-
- for(pTmp = pUsed; pTmp; pTmp = pTmp->pNext) {
- if(pTmp->pObject->GetId() == id) break;
- }
-
- return(pTmp ? pTmp->pObject : NULL);
- }
-
-
- // get child pointer from its native window; it returns NULL if fails
-
- GUI_OBJECT* GUI_WINDOW::GetChildFromWindow(NativeWindow hwnd)
- {
- GUI_OBJECT_ELEM *pTmp;
-
- for(pTmp = pUsed; pTmp; pTmp = pTmp->pNext) {
- if(pTmp->pObject->GetNativeWindow() == hwnd) break;
- }
-
- return(pTmp ? pTmp->pObject : NULL);
- }
-
- /* End of File */
-
-
- Listing 3 demo.c -- Short demonstration program
- #include <stdio.h>
- #include "gui.h"
-
- #define MY_EXIT 1
- #define MY_NEW 2
- #define MY_CLOSE 3
- #define MY_TEXT 4
- #define MY_NAME 5
-
- class MY_BUTTON: public GUI_BUTTON {
- public:
- MY_BUTTON(GUI_WINDOW *pParent, int id, int x=0, int y=0,
- int width=MAX_X, int height=MAX_Y, char *text="")
- :GUI_BUTTON(pParent, id, x, y, width, height, text) {}
- int Activate(void);
- int RightClick(void);
- };
-
- class MY_ENTRY: public GUI_ENTRY {
- public:
- MY_ENTRY(GUI_WINDOW* pParent, int id, int x=0, int y=0,
- int width=MAX_X, int height=0,
- char* text="", int length=10)
- :GUI_ENTRY(pParent, id, x, y, width, height, text,
- length) {}
- int Activate(void);
- };
-
- GUI_APPLICATION *pApplication;
-
- void CreateMyWindow(int id);
- void DeleteMyWindow(GUI_WINDOW *pWindow);
-
- void main(int argc, char **argv)
- {
- pApplication = new GUI_APPLICATION(&argc, argv, "demo.log");
- CreateMyWindow(1);
- pApplication->MainLoop();
- delete pApplication;
- }
-
- int MY_BUTTON::Activate(void)
-
- {
- static int windowCount = 1;
-
- switch(GetId() - pParent->GetId()) {
- case MY_EXIT:
- pApplication->Terminate();
- break;
- case MY_NEW:
- if(windowCount < 10) {
- CreateMyWindow(pParent->GetId() + 100);
- windowCount++;
- }
- else {
- GetParent()->Message("Application message",
- "Too many windows");
- }
- break;
- case MY_CLOSE:
- if(--windowCount) DeleteMyWindow(GetParent());
- else pApplication->Terminate();
- break;
- }
- return(0);
- }
-
- int MY_BUTTON::RightClick(void)
- {
- GetParent()->Message("Applicaton Message", "RightClick");
- return(0);
- }
-
- int MY_ENTRY::Activate(void)
- {
- char text[20+1];
-
- GetText(text, 20);
- GetParent()->SetText(text);
- return(0);
- }
-
- void CreateMyWindow(int id)
- {
- int color, childId;
- char title[100];
- GUI_WINDOW *pWindow;
-
- color = id / 100;
- sprintf(title, "Window %d", id);
- pWindow = new GUI_WINDOW(pApplication, id, 15*id, 4900 - 10*id,
- 5000, 5000, color, title,
- FRAME_TITLE FRAME_SIZEABLE FRAME_MAX_BUTTON);
-
- // create buttons, text and entry field
- new MY_BUTTON(pWindow, id+MY_EXIT, 1000, 9000, 2000, 0, "Exit");
- new MY_BUTTON(pWindow, id+MY_CLOSE, 4000, 9000, 2000, 0, "Close");
- new MY_BUTTON(pWindow, id+MY_NEW, 7000, 9000, 2000, 0, "New");
- new GUI_TEXT(pWindow, id+MY_TEXT, 1000, 7500, 2500, 0, "New title:");
- new MY_ENTRY(pWindow, id+MY_MAME, 4000, 7500, 2000, 0, "", 20);
-
-
- // create little figure
- childId = id + MY_NAME;
- //body
- new GUI_LINE(pWindow, ++childId, 7500, 7000, 8000, 6500, color+1, 2);
- new GUI_LINE(pWindow, ++childId, 8500, 7000, 8000, 6500, color+1, 2);
- new GUI_LINE(pWindow, ++childId, 8000, 6500, 8000, 4000, color+1, 2);
- new GUI_LINE(pWindow, ++childId, 7500, 5000, 8000, 4500, color+1, 2);
- new GUI_LINE(pWindow, ++childId, 8500, 5000, 8000, 4500, color+1, 2);
- //left eye
- new GUI_ELLIPSE(pWindow, ++childId, 7700, 2900, 300, 300, color+2,
- FILL_SOLID);
- //right eye
- new GUI_ELLIPSE(pWindow, ++childId, 8300, 2900, 300, 300, color+2,
- FILL_SOLID);
- //nose
- new GUI_LINE(pWindow, ++childId, 8000, 3200, 8000, 3600, color+2, 1);
- //mouth
- new GUI_LINE(pWindow, ++childId, 7900, 3800, 8100, 3800, color+2, 1);
- new GUI_LINE(pWindow, ++childId, 7800, 3700, 7900, 3800, color+2, 1);
- new GUI_LINE(pWindow, ++childId, 8100, 3800, 8200, 3700, color+2, 1);
- //head
- new GUI_ELLIPSE(pWindow, ++childId, 8000, 3200, 1400, 1600, color+1,
- FILL_SOLID);
-
- pWindow->Show();
- }
-
- void DeleteMyWindow(GUI_WINDOW *pWindow)
- {
- int id;
- GUI_OBJECT *pObj;
-
- for(id = MY_EXIT + pWindow->GetId();
- pObj = pWindow->GetChildFromId(id) ;
- id++) delete pObj;
- delete pWindow;
- }
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Two Wildcard Matching Utilities
-
-
- Mike Cornelison
-
-
- Mike Cornelison is a systems programmer and occasional consultant working in
- Santa Clara, CA. He may be reached through the internet at
- micro@Ix.netcom.com, by phone at 408-970-5294, or by fax at 408-942-9180.
-
-
-
-
- Introduction
-
-
- Have you ever had to switch to a new software development environment, only to
- find that some of your favorite tools were missing in the new environment?
- This has happened to me several times (I have been programming since 1966).
- Most recently, I moved from VAX/VMS to Microsoft Windows and Windows/NT, and I
- found myself missing the powerful VMS string matching and file searching
- functions.
- The VMS string compare function (str$match_wild) can handle any number of
- wildcard characters, and find any possible match with a candidate string. This
- function recognizes the wildcard character '*', which matches any number of
- characters (including zero), and '%', which matches any single character. For
- example, the wildcard string "*abc*def*gh%" would match all of the following
- candidate strings: "abcdefghi", "XXabcXXdefXXXghX", and
- "abcXdefXXghiXXXabcdefghi" (the last one is subtle).
- The VMS directory search function (lib$find_file) can handle the same wildcard
- notation, in both the directory tree being searched (the path), and in the
- file name being searched for within the path. In addition, the notation "..."
- may be used to indicate an arbitrary number of subdirectories within the path.
- For example, the directory and file specification "[aa*...*bb...]ccc.*" means:
- search all directories with names begining with "aa", and their subdirectory
- trees, for directories with names ending in "bb". Search these directories,
- and their subdirectory trees, for file names matching "ccc.*".
- As it turns out, it is not very difficult to make similar tools for the
- Windows and NT environments. That is the subject of this article.
-
-
- MatchWild Function
-
-
- This function compares a candidate string to a wildcard string -- one
- containing any number of the wildcard characters '*' and '?'. The function
- returns TRUE if a match is found, or FALSE if not (See Listing 1). MatchWild
- makes no use of system libraries, hence it will work in any system having a C
- compiler.
- The logic works as follows: the two strings are scanned in parallel. For each
- segment between '*'s in the wildcard string, MatchWild must be able to find a
- corresponding matching segment in the candidate string. If a given comparison
- fails, and the wild segment began with '*', then the function may still search
- for a matching segment later in the candidate string. This fairly simple logic
- will discover any possible match.
- My first version of MatchWild used recursion and avoided the unfashionable
- goto. It was, however, no simpler than the current version, and likely much
- slower. Some benchmark execution times are included in Listing 1.
-
-
- The SearchWild Function
-
-
- This function (Listing 2) searches a directory tree for a desired file or set
- of files. The directory tree is specified as a path name, using the
- traditional notation of DOS, Windows, and Windows/NT. SearchWild's objective
- is to find all possible files in all possible paths that match a given path
- and file name, where both the path and file names may contain the wildcards
- '*' and '?'. This function also allows a third wildcard notation, the
- VMS-style "..." to indicate any number of nested subdirectories. The path and
- file name notation is best clarified with an example:
- d :\aaa*...\*bbb...\cc*.d??
- This means: disk drive d, all top level directories matching "aaa*", all
- underlying subdirectories matching "*bbb" (with any number of in-between
- subdirectories), all underlying files matching "cc*.d??" (again with any
- number of in-between subdirectories). The following two files would match this
- specification:
- d:\aaa\bbb\cc.d12
- d:\aaaxx\xxxx\yyyy\xxbbb\xxx\ccxx.d23
- At first glance, SearchWild seems simple to implement. After all, DOS,
- Windows, and Windows/NT all offer basic directory search functions
- (FindFirstFile and FindNextFile) which are capable of some wildcard handling.
- Specifically, these functions accept wildcards in the last name of a path,
- which may be a file name or another directory name. A program could use these
- functions to iteratively search down several levels of directory names
- containing wildcards, one level at a time. Using the above example, the search
- would start with "d:\aaa*" to find the desired top-level directories. Within
- each directory found (e.g. "aaaxx"), the search would then progress to
- directories and files at the next level down, (e.g. "d:\aaaxx\*") and these
- could be matched to the next desired name "*bbb", and so on. It would all be
- easy, if not for the "..." notation. Implementing this last feature requires a
- more sophisticated approach.
- SearchWild uses recursion to make the messy logic into something almost
- simple. It's entire logic is summarized as follows:
- 1. Replace any "\xxx...\" notation with the equivalent "\xxx\...A" ("xxx") may
- also contain the simpler wildcards '*' and '?')
- 2. If no wildcards are found except in the last name (file name), then use the
- OS-provided search function to find the files and return all of them to
- caller. Done.
- 3. Truncate the path name after the first name having any of the wildcards '*'
- or '?' or "\...\"
- 4. If the wildcard is not "\...\"
- a. Call the OS search function to get all matching file names at this level
- b. Substitute each of these names for the wildcard name, and append remaining
- path\file names truncated from step #3 above
- c. Call SearchWild with each of these path\file names, return all found files
- to caller. This is a recursive call, since SearchWild calls itself
- 5. If the wildcard is "\...\"
- a. Replace "\...\" with "\". ("\...\" can mean zero or more levels of
- subdirectories)
- b. Call SearchWild (recursively) with this name, return all found files to
- caller
- c. Replace "\...\" with "\*\...\"
- d. Call SearchWild (recursively) with this name, return all found files to
- caller
- SearchWild returns one file per call until no more files are found, then it
- returns NULL. The logic depicted above is realized by going back to the
- current position in the code, after each new entry from the caller.
- The recursive calls to SearchWild result in efficient execution since only the
- necessary directories are searched, and no others.
- I have tested the code in Listing 2 for Windows/NT. It should work for DOS or
- Windows 3.1, with only minor adjustments. Note that the OS functions
- FindFirst/Next support multiple search contexts, which is necessary for this
- method to work. SearchWild does not support multiple contexts. This could be
- done, using dynamic allocation of memory for each new context. If the caller
- abandons a search before it is completed, memory can be lost. Hence, another
- call type is needed, to allow the caller to abandon a search and recover the
- dynamic memory.
- I welcome your questions or suggestions. Please contact me at the phone number
- or e-mail address shown in my bio.
-
- Listing 1 String matching utility
- /* ------------------------------------------------------------
-
-
- int MatchWild (const char * wildstr, const char * candstr)
-
- Match candidate string to wildcard string containing any number
- of '*' or '?' wildcard characters. The '*' matches any number of
- characters, including zero characters. The '?' matches exactly
- one character. Returns 1 if there is a match, else 0.
-
- Benchmarks for Pentium/60:
-
- WILDCARD STRING CANDIDATE STRING Microsecs.
- asdfghjkl asdfghjkl 1.5
- *asdfgh XXXasdfgh 2.0
- asdfgh* asdfghXXX 1.8
- *aaa*bbb*ccc* XaaaXXbbbcccXXX 4.1
- asd??fgh??* asdXXfghXX 2.7
- *aa??a*bbb XXaaXXaXXbbXaaXXabbb 5.5
-
- */
-
-
- int MatchWild(const char * pWild, const char * pString)
- {
- int ii, star;
-
- new_segment:
-
- star = 0;
- while (pWild[0] == '*')
- {
- star = 1;
- pWild++;
- }
-
- test_match:
-
- for (ii = 0; pWild[ii] && (pWild[ii] != '*'); ii++)
- {
- if (pWild[ii] ! = pString[ii])
- {
- if (! pString[ii]) return 0;
- if (pWild[ii] == '?') continue;
- if (! star) return 0;
- pString++;
- goto test_match;
- }
- }
-
- if (pWild[ii] == '*')
- {
- pString += ii;
- pWild += ii;
- goto new_segment;
- }
-
- if (! pString[ii]) return 1;
- if (ii && pWild[ii-1] == '*') return 1;
- if (! star) return 0;
- pString++;
-
- goto test_match;
- }
-
-
- /* End of File */
-
-
- Listing 2 Directory search utility
- /* ------------------------------------------------------------
-
- char * SearchWild (const char * wpath, int & ftf)
-
- Search a directory\file path containing wildcards.
- Return each matching file, one file per call.
- Both the directory path and file name may contain
- multiple wildcards, e.g. D:\AA*\...\*BB\CC*.E??
-
- * matches zero or more characters
- ? matches one character
- ... matches zero or more directories
- *... matches one or more directories
- A*... matches one or more, beginning with 'A'
-
- wpath: search path, with optional wildcards
- d:\dir1\dir2\... fully qualified
- \dir1\dir2\... use current disk
- dir1\dir2\... use current directory
-
- ftf: must be 1 to initiate a new search,
- do not alter for subsequent calls
-
- return value:
- pointer to returned file name (d:\path\file)
- or NULL if no more files found
-
- */
-
- #include <windows.h>
-
- #define DIRECTORY FILE_ATTRIBUTE_DIRECTORY
- #define maxlev 20 /* max. directory levels */
- #define maxp _MAX_PATH /* max. pathname\filename length */
- #define maxp4 maxp + 4
-
- #define RETURN(nn,mm) { reent[lev] = nn; --lev; return mm; }
-
- char * SearchWild(const char * wpath, int & ftf)
-
-
- {
- static HANDLE ffh[maxlev];
- static WIN32_FIND_DATA ffdata[maxlev];
-
- static int lev = -1, reent[maxlev];
- static int fstat[maxlev], ftf2[maxlev];
- static char rpath[maxp4];
- static char wpath1[maxlev][maxp4],
- wpath2[maxlev][maxp4],
- wpath3[maxlev][maxp4];
-
-
- int stat, ii, jj;
- char * pF, * pF1, * pF2, * pF3;
-
- if (++lev >= maxlev) goto exceed_maxlev; /* track level
- of reentrancy */
-
- if (ftf) ftf = 0; /* 1st time flag */
-
- else switch (reent[lev]) /* re-entry, resume processing */
- { /* in current search context */
- case 1: goto Reentl;
- case 2: goto Reent2;
- case 3: goto Reent3;
- case 4: goto Reent4;
- default: FatalAppExit(0,"reent bug");
- }
- ii = strlen(wpath); /* check wpath arg. */
- if (ii >= maxp) goto path_toolong;
-
- /* get current D:\DIRECTORY... */
-
- stat = GetCurrentDirectory(maxp.wpathl[lev]);
-
- if (! stat) goto no_curr_dirk;
-
- /* if caller path begins "\" use curr. disk + caller path*/
- if (wpath[0] == '\\')
- strcpy(wpath1[lev]+2,wpath);
-
- else if (wpath[2] == '\\') /* if caller path
- begins "d:\" */
- strcpy(wpath1[lev],wpath); /* use caller path only */
-
- else /* else use current
- directory */
-
- { /* + "\" + caller path */
-
- ii = strlen(wpath1[lev]) + strlen(wpath) + 1;
- if (ii >= maxp) goto path_toolong;
- strcat(wpath1[lev],"\\");
- strcat(wpath1[lev],wpath);
- }
-
- pF = wpath1[lev];
- while (pF = strstr(pF,"...\\")) /* replace any "xxx...\" */
- { /* with "xxx\...\" */
-
- if (pF[-1] != '\\')
- {
- for (ii = strlen(pF); ii; ii--) pF[ii+l] = pF[ii];
- pF[0] = '\\';
- }
- pF += 4;
- }
-
- pF1 = Strchr(wpath1[lev], '*'); /*find 1st wild char. in path */
- pF2 = strchr(wpath1[lev],'?');
-
- pF3 = strchr(wpath1[lev], "\\...\\");
- pf = wpath1[lev] + maxp;
- if (pf1) pF = pF1;
- if (pF2 && (pf2 < pF)) pf = pf2;
- if (pF3 && (pF3 < pF)) pF = pF3;
-
- if (pF < strrchr(wpath1[lev],'\\'))
- goto wild_dirk; /* wild char. is in a directory */
-
- /* no wild directories exist - wild filename possible */
-
- /* search files using wpath1 */
-
- ffh[lev] = FindFirstFile(wpathl[lev],&ffdata[lev]);
- if (ffh[lev] == INVALID_HANDLE_VALUE) RETURN(0,0)
- stat = fstat[lev] = 1;
- Reent1:
- if (fstat[lev] == 2)
- stat = FindNextFile(ffh[lev].&ffdata[lev]);
- fstat[lev] = 2;
- if (! stat) RETURN(0,0)
- if (strcmp(ffdata[lev].cFileName,".") == 0)
- goto Reent1; /* ignore "." and ".." directories */
- if (strcmp(ffdata[lev].cFileName,"..") == 0) goto Reentl;
- pF = strrchr(wpath1[lev],'\\'); /* last"\'in search path*/
- ii = strlen(ffdata[lev].cFileName);
- jj = pF - wpath1[lev] + 1;
- if (ii + jj > maxp) goto path_toolong;
- strncpy(rpath,wpath1[lev],jj); /* build directory\filename*/
- strcpy(rpath+jj,ffdata[lev].cFileName);
- RETURN(1,rpath) /* return, re-enter at Reent1 */
-
- /* directory has wild characters */
-
- wild_dirk;
- if (pF[0] == '\\') goto wild_dots0; /* found "\...\" */
-
- /* 1st wild directory contains '*' or '?' */
-
- strcpy(wpath2[lev],wpath1[lev]); /* wpath2 = wpath1 thru
- wild dirk */
- pF = wpath2[lev] + (pF - wpath1[lev]);
- while (pF[0] != '\\') pF++; /* eliminate final '\' */
- pF[0] = 0;
- ffh[lev] =
- FindFirstFile(wpath2[lev],&ffdata[lev]); /* search using wpath2 */
-
- if (ffh[lev] == INVALID_HANDLE_VALUE) RETURN(0,0)
- stat = fstat[lev] = 1;
- Loop2;
- if (fstat[lev] == 2) stat = FindNextFile(ffh[lev],&ffdata[lev];
- fstat[lev] = 2;
- if (! stat) RETURN(0,0)
- if (!(ffdata[lev].dwfileAttributes & DIRECTORY))
- goto Loop2; /* ignore non-directories */
- if (strcmp(ffdata[lev].cFileName,".") == 0)
- goto Loop2; /* ignore "." and ".." directories */
- if (strcmp(ffdata[lev].cFileName,"..") == 0) goto Loop2;
- strcpy(wpath3[lev],wpath2[lev]);
-
- pF = strrchr(wpath3[Lev],'\\') + 1; /* wpath3 = wpath1
- with specific */
- ii = strlen(ffdata[lev].cFileName); /* dirk in place of
- wild dirk */
- ii += pF - wpath3[lev];
- if (ii > maxp) goto path_too long;
- strcpy(pF,ffdata[lev].cFileName);
- pF = wpath1[lev] + (pF - wpath3[lev]);
- pF = strchr(pF,'\\');
- jj =strlen(pF);
- if (ii + jj > maxp) goto path_toolong;
- strcpy(wpath3[lev]+ii,pF);
- ftf2[lev] = 1;
- Reent2:
- pF = SearchWild(wpath3[lev],ftf2[lev]); /* recursive search
- next level */
- if (! pF) goto Loop2;
- RETURN(2,rpath) /* return, re-enter at Reent2 */
-
- /* 1st wild directory is "\...\" */
-
- wild_dots0:
- strcpy(wpath2[lev],wpath1[lev]); /* wpath2 = wpath1 */
- pF = strstr(wpath2[lev],"\\...\\");
- for (ii = 0; pF[ii]; ii++)
- pF[ii+1] = pF[ii+5]; /* repl. "\...\"
- with "\" */
- ftf2[lev] = 1;
- Reent3:
- pF = SearchWild(wpath2[lev],ftf2[lev]); /* recursive search
- next level */
- if (! pF) goto wild_dots1;
- RETURN(3,rpath) /* return, re-enter at Reent3 */
-
- wild_dots1:
- strcpy(wpath2[lev],wpath1[lev]); /* wpath2 = wpath1 */
- pF = strstr(wpath2[lev], "\\...\\");
- for (ii = strlen(pF); ii; ii--)
- pF[ii+2] = pF[ii]; /* repl. "\...\"
- with "\*\...\" */
- strncpy(pF, "\\*",2);
- ftf2[lev] = 1;
- Reent4:
- pF = SearchWild(wpath2[lev],ftf2[lev]); /* recursive search
- next levl */
- if (! pF) RETURN(0,0)
- RETURN(4,rpath) /* return, re-enter at Reent4 */
-
- /* fatal error exits */
-
- exceed_maxlev:
- FatalAppExit(0,"SearchWild: exceed max directory levels");
-
- no_curr_dirk:
- FatalAppExit(0, "SearchWild: GetCurrentDirectory() failed");
-
- path_toolong:
- FatalAppExit(0, "SearchWild: exceed max path length");
- return 0; /* dummy, required by compiler */
-
- }
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Windows Programming Power with Custom Controls
-
-
- Bob Swart
-
-
- Bob Swart is a professional software developer and free-lance technical author
- using Borland Pascak, C++, and Delphi. In his spare time he likes to watch
- video tapes of Star Trek The Next Generation with his 1-year-old son Erik Mark
- Pascal.
-
-
- Windows Custom Controls nowadays come in (at least) two flavors: standard
- custom controls and VBX-style custom controls. Windows Programming Power with
- Custom Controls covers both flavors and is intended for two groups of people:
- users and designers/programmers of custom controls. You don't have to be an
- experienced Windows programmer to learn what custom controls are or how to use
- them. The book explains in-depth the history of standard and VBX-style
- controls. By reading this book you learn what the differences are between
- these two. Unfortunately, no mention of OLE custom controls (OCX-style
- controls, an extention of VBXs) is included.
- To emphasize the difference between standard and VBX-style controls (in design
- as well as in use), the book shows how to create your own custom controls in
- C/C++. This is no doubt the better way to master them! To implement VBX
- controls you need the VB SDK, but don't worry, even without this SDK the book
- is worth reading because it contains a lot of useful information about usage
- of VBX controls in applications.
- The first two chapters describe the historic details of custom controls and
- Windows, written in the exciting Duntemann style. The following chapters
- present custom controls as ideal software components, not just "gadgets," and
- show that VBX controls have use beyond Visual Basic. (For instance, VBX level
- 1.0 controls can be used with Visual C++ and Borland C++, and with Delphi.)
- The third chapter starts the real work, by building a framework or "skeleton"
- custom control. Actually, this skeleton does nothing, but it serves as the
- platform -- with already half the work done -- to build future example
- controls in subsequent chapters. This chapter provides much detail, as it is
- the foundation for the rest of the book.
- Chapter four describes design guidelines and shows some different ways to use
- custom controls in an application. This chapter is really short, being
- preparatory to chapters five through eleven, where a total of seven custom
- contols are implemented in both general and VBX style.
-
-
- The Seven Custom Controls
-
-
- Here are the seven controls presented in the book:
- The Panel Control gives your programs a 3D, high-tech look and feel. This
- control is presented only in the standard custom control format. As a bonus,
- however, the book also presents the control with a Borland Pascal interface
- unit.
- The Virtual Listbox Control can display up to 32K items in a listbox, not
- simply 32K of total data size, as with normal Windows listboxes.
- The Pagelist Control will let you pick one or more page icons from a
- horizontal list of icons.
- The Browser Control is a read-only viewer that lets you examine, among other
- things, database records, much like the Visual Basic Data control.
- The Text File Viewer Control can view up to 32K lines, and can be expanded to
- show even more lines.
- The Text File Editor Control, based on the Text File Viewer, can edit up to
- 32K lines. Compare that to the normal Windows Edit Control that can hold only
- up to 32KB of data. This text file editor supports communication with the
- clipboard and all common text editing features.
- The IniData Control is a special-purpose edit control that manages Windows INI
- files. It even contains a security feature that enables you to encrypt
- selected items from an INI file.
- The source and executables to these controls are included on the accompanying
- disk. In my opinion, they are worth the price of the book by themselves!
- According to the author, book purchasers can treat the source code on the disk
- as a software package with one license. However, there are no royalties
- imposed on run-time applications (or accompanying DLLs and VBXs) developed
- with this source code. So, while you may not distribute the source code, you
- may distribute DLLs, VBXs, or executables without restriction.
- The appendix presents a detailed view of the controls' use. These VBX controls
- will work with Borland C++, Microsoft Visual C++, and Visual Basic. The
- generic version of these custom controls will work in any
- Windowsdevelopmentenvironment.
- The disk contains full C/C++ source code of everything discussed in the book,
- plus project files for Turbo C++ and Borland C++, and makefiles for Visual
- C++. The online documentation includes a windows helpfile for every example
- custom control. The code is of good quality, and is made clear by intelligent
- use of comments. I'm a little disapointed that almost none of the sources are
- specifically C++. I wouldn't mind seeing a C++ class wrapper around the
- skeleton custom control, for example. Of course, readers can easily extend the
- generic C source code itself.
-
-
- Do It Yourself
-
-
- This book will teach you the inner workings of Windows custom controls. The
- included C/C++ skeleton custom control enables you to build your own custom
- controls, while the book's highly detailed examples will help you master the
- entire process from start to finish. I recommend this book for all serious
- windows programmers.
- Title: Windows Programming Power with Custom Controls
- Authors: Paul Cilwa & Jeff Duntemann
- Publisher: The Coriolis Group (1994)
- Pages: 496
- Price: $39.93 (with 3.5" disk)
- ISBN: 1-883577-00-4
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Standard C/C++
-
-
- The Header <fstream>
-
-
-
-
- P.J. Plauger
-
-
- P.J. Plauger is senior editor of C/C++ Users Journal. He is convener fo the
- ISO C standards committee, WG14, and active on the C++ committee, WG21. His
- latest books are The Draft Standard C++ Library, and Programming on Purpose
- (three volumes), all published by Prentice-Hall. You can reach him at
- pjp@plauger.com.
-
-
-
-
- Introduction
-
-
- I've been working my way through the iostreams portion of the draft Standard
- C++ library, lo these many months. Interestingly enough, I've yet to get to
- the part that programmers probably use the most -- reading and writing streams
- of text connected to external files and devices. This installment still
- doesn't get as far as cin and cout, those ubiquitous standard streams we all
- know and love. But it does lay some important groundwork for them. At last we
- will see how to specialize a stream buffer for controlling input and output.
- The header <fstream> defines half a dozen classes. Three of these cooperate to
- help you read and write files that you open by name:
- filebuf, derived from streambuf to mediate access to an external stream of
- characters read from or written to a file
- ifstream, derived from istream to construct a filebuf object and to assist in
- extracting from the stream
- ofstream, derived from ostream to construct a filebuf object and to assist in
- inserting into the stream
- The other three classes defined in <fstream> cooperate to help you read and
- write files controlled by an object of type FILE, declared in <stdio.h>:
- stdiobuf, derived from streambuf to mediate access to a stream of characters
- read from or written to an external stream under control of a FILE object
- istdiostream, derived from istream to construct a stdiobuf object and to
- assist in extracting from the stream
- ostdiostream, derived from ostream to construct a stdiobuf object and to
- assist in inserting into the stream
- Listing 1 shows the way I chose to implement the header <fstream>. I present
- it here, without explanation, for those of you who feel better looking at
- concrete code. Next month, I discuss in more detail how to implement this
- header.
- Reading and writing files is an important part of iostreams. For many C++
- programs, it is the sole means of communication between a program and the
- outside world. The standard stream objects, such as cin and cout, are
- conventionally associated with external streams. Even in this era of
- window-oriented interfaces, reading and writing files remains important.
- Communication between two programs, between a program and a window, or between
- a program and a special device are all often made to look like conventional
- file reads and writes. The classes defined in <fstream> are the preferred
- agents for controlling such file operations.
- You do not, in principle, need two sets of classes for accessing files with
- iostreams. The Standard C library function fopen, declared in <stdio.h>,
- associates a named file with a FILE object. The same header declares fclose,
- for removing the association. You can associate a FILE object with a
- stdiostream object, as described above, and you're in business. Why the extra
- machinery?
-
-
- A Bit of History
-
-
- The answer is largely historical. The iostreams package evolved alongside the
- Standard C library more than atop it. Both spent their earliest days hosted on
- the UNIX operating system, a particularly friendly environment for reading and
- writing files. As they have spread to other systems, both have also profited
- from the influence of UNIX on more modern opeaating systems. Thus, iostreams
- and the Standard C library have common heritage, and many common architectural
- features, but they nevertheless grew up separately.
- As a consequence, mixing iostreams and C stream operations has often led to an
- uneasy alliance. A filebuf object often performs reads and writes directly,
- using low-level operating system calls and managing an in-memory buffer
- directly. A stdiobuf object, by contrast, typically calls on the higher-level
- functions declared in <stdio.h>. The overhead per character can be much higher
- and buffering can occur in two different places within the program.
- C++ programmers habitually favor the former over the latter, if only for
- better performance. They introduce stdiobuf objects only when obliged to mix
- iostreams and C stream reads or writes to the same file. And then they fret
- over the uncertainties that inevitably arise with double buffering. Input can
- be consumed in greater chunks than you expect, by the two agents. Or output
- from the two agents can get pasted together in bigger chunks than you intend.
- Often, the only safe fix is to make stdiobuf operations completely unbuffered.
- And that often penalizes performance even more.
- The draft C++ Standard addresses these historical problems:
- It defines the semantics of class filebuf as if it performs reads and writes
- through a FILE object. The draft C++ Standard thus rides atop the detailed
- descriptions of file operations from the C Standard. And it gives quick
- answers to many questions about the effect of mixing iostreams and C stream
- operations.
- It defines class filebuf in such a way that no FILE object is directly
- visible. The draft C++ Standard thus permits an implementation of filebuf atop
- the Standard C library, without mandating it.
- It retains class stdiobuf, which is defined explicitly in terms of a visible
- FILE object. Operations are unbuffered by default, so programs have no
- synchronization surprises.
- It nevertheless provides member functions to control whether stdiobuf
- operations can be buffered, if performance is more important to you than tight
- synchronization between iostreams and C stream operations.
- It specifies that the standard objects cin, cout, cerr, and clog behave as if
- they designate unbuffered stdiostream objects, without requiring exactly that
- implementation.
- The basic idea is to better define the relationship between iostreams and C
- stream operations, and to provide as a default less surprising semantics for
- mixed operations. Nevertheless, the draft C++ Standard does not require that
- existing implementations of iostreams be rewritten purely in terms of calls to
- Standard C library functions.
- One example of shared semantics is the way you open a file by name. The
- filebuf member function open(const char *, ios::openmode) is the agent that
- does the job. It effectively calls fopen, declared in <stdio.h>. To do so, it
- must map its second argument to the kind of mode string acceptable to fopen.
- Thus, for example, ios::in becomes "r". The member function is obliged to
- accept only those combinations of ios::openmode elements for which a
- corresponding mode string is defined.
- Another example is the semantics of stream positioning. The Standard C library
- permits a FILE object to mediate both reads and writes, but only in a rather
- stylized way. At any given time, the input stream may be readable or the
- output stream may be writable, but not both. (Of course, it is also possible
- that neither stream is available.) Typically, you have to perform a
- stream-positioning operation to switch between reading and writing. Equally,
- only one stream position is maintained for both reading and writing. And an
- arbitrary stream position requires the unspecified information stored in an
- fpos_t object, not just a streamoff value.
- Iostreams implicitly acknowledges these limitations in several ways. By
- inheritance, it has the same semantic limitations on switching between
- extracting and inserting. (Opinions differ on this point within the Committee,
- however.) If you attempt to position either stream in a filebuf object, you
- position both at once. And the semantics of class streampos reflect the
- realities of the restrictions on fpos_t objects. (See "Standard C: The Header
- <streambuf>," CUJ, June 1994.)
- I don't want to paint too rosy a picture, however. Several Committee members
- begrudge almost any concession to the Standard C library in the area of file
- operations. They may accept the narrow need for defining the semantics of the
- two libraries when they work together. But some feel that the Standard C++
- library is better off being defined de novo in this area. I personally don't
- see how to introduce new semantics for iostreams without massively
- complicating the description of, say, stdiobuf. Others, however, may find a
- way over time.
-
-
- Recent Changes
-
-
- The description of <fstream> depends heavily on references to functions and
- types declared in <stdio.h>, for reasons I indicated above. Please note once
- more, however, that such language makes no promises about how classes in this
- header are actually implemented. The draft C++ Standard often says, for
- example, that function A calls function B. Generally, this means only that A
- behaves as if it calls B. If you can write a portable program that can detect
- whether the call occurs -- such as to a virtual member function that you can
- override -- the call may be obligatory. Otherwise, don't be surprised if an
- interactive debugger fails to detect an actual call.
- As I mentioned in conjunction with the header <streambuf> class streambuf has
- an added virtual member function. The public access function for it is called
- showmany. It endeavors to tell you how many characters you can safely extract
- with no fear of blocking while waiting for additional input. The derived
- classes filebuf and stdiobuf should have nontrivial overrides for this virtual
- member function, on systems that can supply the needed information.
-
- Once again, I note that a major change to all of iostreams is the addition of
- wide-character streams. The classes filebuf and stdiobuf become template
- classes parameterized by the type of the stream element. One instantiation,
- for type char, has essentially the same functionality as described here.
- Another, for type wchar_t, supports streams of elements from some large
- character set. Classes ifstream, ofstream, istdiostream, and ostdiostream
- change along similar lines.
- As before, I continue to present an implementation written just to handle char
- streams. It illustrates all the basic issues, without the additional
- complexity of templates.
- Finally, I must report that the classes stdiobuf, istdiostream, and
- ostdiostream have been voted out of the draft C++ Standard. Since they occur
- widely in existing practice, however, I suspect that many vendors will still
- choose to supply them. I, for one, am not quick to burn this useful bridge
- between the worlds of C and C++.
-
-
- Using <fstream>
-
-
- You include the header <fstream> to make use of any of the classes ifstream,
- ofstream, filebuf, istdiostream, ostdiostream, or stdiobuf. Objects of these
- classes let you read and write conventional files. You can open files by name
- and control them, or control files already opened under control of objects of
- type FILE. For each approach, you can choose among three patterns of access:
- read only
- write only
- read/write
- I deal with each of these options in turn, first for files you open by name.
-
-
- Read Only, By Name
-
-
- If all you want to do is open and read an existing text file whose name you
- know, construct an object of class ifstream. If you know at construction time
- what null-terminated file name s you wish to use, you can write:
- ifstream fin(s);
- if (fin.is_open())
- <file opened successfully>
- If the file is not opened successfully, subsequent extractions will fail.
- Note, however, that the conventional tests for failure, !fin or fin != 0, will
- not be false until after you essay such an extraction. That's why I encourage
- you to make the explicit test fin. is_open() immediately after the object is
- constructed.
- The resultant stream buffer (pointed at by fin.rdbuf()) does not support
- insertions. You can, however, close any currently open file, then open the
- file s2 for reading with the two calls:
- fin.close(), fin.open(s2);
- Naturally, you should once again test whether the open succeeded, as above.
- The stream position is reset to the beginning of the newly opened stream. (And
- the resultant stream buffer still does not support insertions.)
- You can also construct an ifstream object with no open file, using the default
- constructor. Presumably, you would later open an existing text file for
- reading, as in:
- ifstream fin;
- fin.open(s);
- if (fin.is_open())
- <file opened successfully>
- Destroying an ifstream object closes any open file associated with it.
- The code I have shown so far always opens a text file, for reading only. A
- text file can be subject to a certain amount of interpretation, such as
- mapping the sequence carriage return/line feed to just line feed (newline). A
- binary file, on the other hand, delivers each byte from the file unchanged as
- a char value. To read a binary file, to make a file writable as well, or to
- invoke various other options when you open a file, you have to specify an
- explicit open-mode argument. (Naturally enough, it has type ios::openmode.)
- For all member functions that take a file-name argument s, the open-mode mode
- immediately follows. The first example, above, is actually equivalent to:
- ifstream fin(s, ios::in);
- if (fin.is_open())
- <file opened successfully>
- You have a number of options for the value of mode:
- ios::in, to open an existing text file for reading
- ios::out / ios::trunc, to create a text file or to open and truncate an
- existing text file for writing
- ios::out / ios::app, to create a text file or to open an existing text file
- for writing, where each write occurs at the end of the file
- ios::in / ios::binary, to open an existing binary file for reading
- ios::out / ios::trunc / ios::binary, to create a binary file or to open and
- truncate an existing binary file for writing
- ios::out / ios::app / ios::binary, to create a binary file or to open an
- existing binary file for writing, where each write occurs at the end of the
- file
- ios::in / ios::out, to open an existing text file for reading and writing
- ios::in / ios::out / ios::trunc, to create a text file or to open and truncate
- an existing text file for reading and writing
- ios::in / ios::out / ios::app, to create a text file or to open an existing
- text file for reading and writing, where each write occurs at the end of the
- file
- ios::in / ios::out / ios::binary, to open an existing binary file for reading
- and writing
- ios::in / ios::out / ios::trunc / ios::binary, to create a binary file or to
- open and truncate an existing binary file for reading and writing
- ios::in / ios::out / ios::app / ios::binary, to create a binary file or to
- open an existing binary file for reading and writing, where each write occurs
- at the end of the file
- If you also set ios::ate in mode, the file is positioned at end-of-file
- immediately after it is opened.
-
-
- Write Only, By Name
-
-
- If all you want to do is create a new text file -- or truncate an existing
- text file -- then open it for writing, construct an object of class ofstream
- to control insertions into it. You can write:
- ofstream fout(s);
- if (fout.is_open())
- <file opened successfully>
-
- then insert into fout just like any other output stream. As with class
- ifstream, you can follow the file name s with a mode argument. If you omit the
- mode argument, as above, it defaults to ios::out.
- You can also construct an ofstream object with the default constructor and
- later create it for writing, as in:
- ofstream fout;
- fout.open(s);
- if (fout.is_open())
- <file opened successfully>
- In either case, the resultant stream buffer (pointed at by fout. rdbuf()) does
- not support extractions. And, of course, destroying an ofstream object closes
- any open file associated with it.
-
-
- Read/Write, By Name
-
-
- If you want to open a file that you can read as well as write, you need two
- objects to control the input and output streams. The classes ifstream and
- ofstream are highly symmetric, at least in this regard. Thus, you have three
- equally valid ways to do the job. If you don't want to open a file initially,
- you can write:
- ifstream ifile;
- ostream ofile(ifile.rdbuf());
- or:
- ofstream ofile;
- istream ifil e(ofile.rdbuf());
- or:
- filebuf fb;
- istream ifile(&fb);
- ostream ofile(&fb);
- All approaches cause ifile to control the input stream and ofile to control
- the output stream.
- You can also open a file s in each of these three cases. Since the default
- values for the mode argument rarely make sense here, I show the argument
- explicitly in each case:
- ifstream ifile(s, mode);
- ostream ofile(ifile.rdbuf());
- if (ifile.is_open())
- <file opened successfully>
- or:
- ofstream ofile(s, mode);
- istream ifile(ofile.rdbuf());
- if (ofile.is_open())
- <file opened successfully>
- or:
- filebuf fb;
- istream ifile(&fb);
- ostream ofile(&fb);
- if (fb.open(s, mode))
- <file opened successfully>;
- Note that the last test for a successful open differs from the earlier ones.
- As usual, when the filebuf object is destroyed, any open file associated with
- it is closed.
-
-
- Controlling C Streams
-
-
- The classes istdiostream, ostdiostream, and stdiobuf provide additional
- capability within the header <fstream>. They let you control files already
- opened under control of an object of type FILE. For example, the function
- fopen, declared in <stdio.h>, returns a non-null pointer to FILE when it
- successfully opens a file. Numerous other functions, declared in the same
- header, support C stream reads and writes to the opened file.
- The same header also declares three well known objects of type pointer to FILE
- that control the three standard streams:
- stdin, controlling the standard input stream
- stdout, controlling the standard output stream
- stderr, controlling the standard error stream
- The header <iostream> declares several istream and ostream objects that work
- in concert with these objects to support iostreams operations on the standard
- streams. You can nevertheless use the facilities in <fstream> to control, say,
- stdout with an additional object you construct.
- As usual, there are three patterns of access to discuss: read only, write
- only, and read/write. I cover them in order.
-
-
- Read Only
-
-
- If all you want to do is read a stream controlled by a FILE object, construct
- an object of class istdiostream. You must know at construction time the
- argument value pf, of type pointer to FILE. You can write:
- istdiostream fin(pf);
-
- If pf is a null pointer, or if the stream it controls cannot be read, all
- subsequent insertion operations will fail. You cannot, however, test whether
- fin is associated with an open file.
- When fin is destroyed, the stream *pf is not closed. Nor should you close the
- stream, by calling fclose(pf), before fin is destroyed. The call discredits
- pf, so even a subsequent attempt to access the pointer itself can cause a
- program to terminate abnormally. Worse, subsequent attempts to control the
- file may do all sorts of insane things that are not diagnosed.
- You can control the degree of buffering within fin. Initially, fin.buffered()
- returns zero, indicating that no buffering occurs. Put simply, you can
- alternate the calls fin.get() and fgetc(pf) and read alternate characters from
- the file.
- Once you call fin.buffered(1), however, fin.buffered(1) returns a nonzero
- value. Thereafter, buffering may occur. Put simply, the call fin.get() may
- encourage the stream buffer associated with fin to gobble an arbitrary number
- of characters, not just the one you requested. A subsequent call to fgetc(pf)
- will not necessarily deliver the next character you would expect.
- If you resist the temptation to access *pf directly, buffering causes no
- problems. On the contrary, it offers the controlling stream buffer the
- opportunity to improve performance, sometimes considerably. A wise rule of
- thumb, therefore, is never to enable buffering for a file accessed both via a
- stream buffer and via C stream function calls. If the stream buffer is the
- sole agent accessing the file, always enable buffering.
-
-
- Write Only
-
-
- If all you want to do is write a stream controlled by a FILE object, construct
- an object of class ostdiostream. You must know at construction time the
- argument value pf, of type pointer to FILE. You can write:
- ostdiostream fout(pf);
- If pf is a null pointer, or if the stream it controls cannot be written, all
- subsequent insertion operations will fail. As with an istiodstream object, you
- cannot test whether fout is associated with an open file. The same remarks
- also apply about not closing the file until fout is destroyed. Equally, the
- same considerations apply about when to buffer, or not to buffer, a stream
- associated with an ostdiostream object.
-
-
- Read/Write
-
-
- Finally, if you want to both read and write a stream controlled by a FILE
- object, you need two objects to control the input and output streams. The
- classes istdiostream and ostdiostream are highly symmetric. Thus, you have
- three equally valid ways to do the job:
- istdiostream ifile(pf);
- ostream ofile(ifile.rdbuf());
- or:
- ostdiostream ofile(pf);
- istream ifile(ofile.rdbuf());
- or:
- stdiobuf sb(pf);
- istream ifile(&sb);
- ostream ofile(&sb);
- In the third case, you enable buffering by calling sb. buffered(1).
-
-
- File Positioning
-
-
- Earlier, I discussed the limitations on positioning within files. Your safest
- bet, as always, is to memorize a file position you want to return to, as an
- object of type streampos. Later on in the program, while the file is still
- open, you can use the value stored in this object to return to the memorized
- file position.
- For a binary file that is not too large, you can represent a stream position
- as an object of type streamoff. You can thus perform arithmetic, on byte
- displacements from the beginning of a file, to determine new stream positions.
- The UNIX operating system represents text files the same as binary. Hence, it
- extends the same latitude in stream positioning to all files, not just binary.
- But few other systems share this simplicity. Don't write portable code that
- counts on it.
- A file opened both for reading and writing requires intervening
- stream-positioning requests when switching from reading to writing, or back.
- Again, some systems may relax this requirement, but don't count on it in a
- portable program.
- Finally, the streambuf virtual member functions setbuf and sync are given
- non-trivial semantics in the derived class filebuf. The former, however, is
- defined in terms of the function setvbuf, declared in <stdio.h>, which does
- not itself promise much. And the latter is generally called as often as
- necessary in the normal course of business. I recommend, therefore, that you
- not call either pubsetbuf or pubsync, the public member functions that call
- these virtual member functions on your behalf.
- This article is excerpted in part from P.J. Plauger, The Draft Standard C++
- Library, (Englewood Cliffs, N.J.: Prentice-Hall, 1995).
-
- Listing 1 The header <fstream>
- // fstream standard header
- #ifndef _FSTREAM_____LINEEND____
- #define _FSTREAM_____LINEEND____
- #include <istream>
- #include <ostream>
- // class filebuf
- struct _Filet;
- class filebuf : public streambuf {
- public:
- filebuf(_Filet *_F = 0)
- {_Init(_F); }
- filebuf(ios::_Uninitialized)
- : streambuf(ios::_Noinit) {}
- virtual ~filebuf();
- bool is_open() const
- {return ((_File != 0)); }
- filebuf *open(const char *, ios::openmode);
- filebuf *open(const char *_N, ios::open_mode _M)
-
- {return (open(_N, (ios::openmode)_M)); }
- filebuf *close();
- protected:
- virtual int overflow(int = EOF);
- virtual int pbackfail(int = EOF);
- virtual int underflow();
- virtual int uflow();
- virtual streamsize xsgetn(char *, streamsize);
- virtual streamsize xsputn(const char *, streamsize);
- virtual streampos seekoff(streamoff, ios::seekdir,
- ios::openmode = (ios::openmode)(ios::in ios::out));
- virtual streampos seekpos(streampos,
- ios::openmode = (ios::openmode)(ios::in ios::out));
- virtual streambuf *setbuf(char *, streamsize);
- virtual in sync();
- _Filet *_Init(_Filet * = 0, bool = 0);
- private:
- bool _Closef;
- _Filet *_File;
- };
- // class ifstream
- class ifstream : public istream {
- public:
- ifstream()
- : istream(&_Fb) {}
- ifstream(const char *_S, openmode _M = in)
- : istream(&_Fb) {_Fb.open(_S, _M); }
- virtual ~ifstream();
- filebuf *rdbuf() const
- {return ((filebuf *)&_Fb); }
- bool is_open() const
- {return (_Fb.is_open()); }
- void open(const char *_S, openmode _M = in)
- {if (_Fb.open(_S, _M) == 0)
- setstate(failbit): }
- void open(const char *_S, open_mode _M)
- {open(_S, (openmode)_M); }
- void close()
- {if (_Fb.close() == 0)
- setstate(failbit); }
- private:
- filebuf _Fb;
- };
- // class ofstream
- class ofstream : public ostream {
- public:
- ofstream()
- : ostream(&_Fb) {}
- ofstream(const char *_S, openmode _M = out trunc)
- : ostream(&_Fb) {_Fb.open(_S, _M); }
- virtual ~ofstream();
- filebuf *rdbuf() const
- {return ((filebuf *)&_Fb); }
- bool is_open() const
- {return (_Fb.is_open()); }
- void open(const char *_S, openmode _M = out trunc)
- {if (_Fb.open(_S, _M) == 0)
- setstate(failbit); }
- void open(const char *_S, open_mode _M)
-
- {open(_S, (openmode)_M); }
- void close()
- {if (_Fb.close() == 0)
- setstate(failbit); }
- private:
- filebuf _Fb;
- };
- // class stdiobuf
- class stdiobuf : public filebuf {
- public:
- stdiobuf(_Filet *_F)
- : filebuf(_F), _Is_buffered(0) {}
- virtual ~stdiobuf();
- bool buffered() const
- {return (_Is_buffered); }
- void buffered(bool _F)
- {Is_buffered = _F; }
- private:
- bool _Is_buffered;
- };
- // class istdiostream
- class istdiostream : public istream {
- public:
- istdiostream(_Filet *_F)
- : istream(&_Fb), _Fb(_F) {}
- virtual ~istdiostream();
- stdiobuf *rdbuf() const
- {return ((stdiobuf *)&_Fb); }
- bool buffered() const
- {return (_Fb.buffered()); }
- void buffered(bool _F)
- {_Fb.buffered(F); }
- private:
- stdiobuf _Fb;
- };
- // class ostdiostream
- class ostdiostream : public ostream {
- public:
- ostdiostream(_Filet *_F)
- : ostream(&_Fb), _Fb(_F) {}
- virtual ~ostdiostream();
- stdiobuf *rdbuf() const
- {return ((stdiobuf *)&_Fb); }
- bool buffered() const
- {return (_Fb.buffered()); }
- void buffered(bool _F)
- {_Fb.buffered(_F); }
- private:
- stdiobuf _Fb;
- };
- #endif /* _FSTREAM_ */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Code Capsules
-
-
- A Better C
-
-
-
-
- Chuck Allison
-
-
- Chuck Allison is a regular columnist with CUJ and a Senior Software Engineer
- in the Information and Communication Systems Department of the Church of Jesus
- Christ of Latter Day Saints in Salt Lake City. He has a B.S. and M.S. in
- mathematics, has been programming since 1975, and has been teaching and
- developing in C since 1984. His current interest is object-oriented technology
- and education. He is a member of X3J16, the ANSI C++ Standards Committee.
- Chuck can be reached on the Internet at 72640.1507@compuserve.com.
-
-
- Bjarne Stroustrup, the inventor of C++, has suggested three ways to look at
- C++:
- 1) as a better C
- 2) as a language that supports data abstraction, and
- 3) as an object-oriented programming language
- Because C++ truly is all of these things, it is somewhat more complex than
- most popular programming languages, especially the other object-oriented ones.
- The proponents of SmallTalk, Eiffel and CLOS (object-oriented Common LISP)
- engage in much religious debate about purity, simplicity, and the "true"
- definition of the term "object-oriented." But C++ is in fact a multi-paradigm
- language -- it supports the traditional procedural style of programming, like
- C and Pascal do; like Ada it supports data abstraction and generics
- (templates); and it supports inheritance and polymorphism, like all the other
- object-oriented languages. All this may make for somewhat of an impure
- programming language, but it also makes C++ the more practical choice for
- production programming. C++ unquestionably gives the best performance,
- functions well in mixed-language environments (not just with C, but with
- FORTRAN, COBOL, and other languages, as well), and does not require the
- enormous resources that Smalltalk, Eiffel and LISP do (the latter being
- environments, not just compile-and-link processes).
- In this article I explore the non-object-oriented features of C++ that make it
- "a Better C." Remember, however, that C++ came about by adding classes to C --
- in other words, by adding data abstraction support to C. It is difficult to
- motivate some of the Better-C features without showing their class-based
- origins. I therefore use the class mechanism of C++ liberally.
-
-
- The Type System
-
-
- Perhaps the most important thing to understand about C++ is its devotion to
- type safety. The OO languages mentioned above are very weakly typed, if at
- all, because they perform error-checking mainly during execution. C++, on the
- other hand, requires you to declare the type of every variable, and the types
- of the parameters and the return types of every function. It fastidiously
- checks your usage of these objects at compile time. It is type safety, more
- than anything else, that makes C++ a Better C, and the most reasonable choice
- for most programming tasks.
-
-
- Function Prototypes
-
-
- ANSI-C-style function prototypes are not optional in C++. In fact, the
- prototype mechanism was invented for C++ before the ANSI C committee adopted
- it. You must either declare or fully define each function before its first
- use. The compiler will check each function invocation for the correct number
- and type of arguments. In addition, it will perform automatic conversions
- where they apply. The program in Listing 1 shows a common error that occurs in
- C when you don't use prototypes. The function dprint expects a double
- argument. Without knowing dprint's prototype, the compiler doesn't know that
- the call dprint(123) is an error. When you provide the prototype for dprint,
- the compiler automatically converts 123 to a double for you (see Listing 2).
- Since C++ allows types that you define to behave like built-in types, it
- allows implicit conversions for user-defined types as well. Since the
- constructor for struct A in Listing 3 expects a double argument, the compiler
- automatically converts the integer 1 to a double in the definition for a. The
- call f(2) generates the equivalent of the following actions:
- 1. convert 2 to a double
- 2. initialize a temporary A object with the value 2.0
- 3. pass that object to f
- In other words, the compiler generates code equivalent to:
- f(A(double(2)) );
- Note C++'s function-style cast syntax. The expression
- double(2)
- is equivalent to
- (double) 2
- Only one implicit user-defined conversion is allowed in any expression,
- however. The program in Listing 4 requires you to provide a B object to
- initialize an A object. A B object in turn requires a double, because its only
- constructor is B::B(double). The expression
- A a(1)
- becomes:
- A a(B(double(1)))
- which has only one user-defined conversion. The expression f(3), however, is
- invalid, because it would require the compiler to provide two automatic
- user-defined conversions:
- // Can't do both an A and a B
- // conversion implicitly
- f(A(B(double(3))) // invalid
- The expression f(B(3)) is okay, because you explicitly requested the
- conversion B(double(3)), so the compiler provides only the remaining
- conversion to A.
-
-
- Type-Safe Linkage
-
-
- C++ can even detect improper function calls across compilation units. The
- program in Listing 5 calls a function in Listing 6. When compiled as a C
- program, the situation is analogous to Listing 1, giving the output:
-
- f:0.000000
- C has no way of knowing that the f's are different. The conventional
- work-around is to put the correct prototype in a header file that all
- compilation units include. In C++, a function call will only link with a
- function that has the same signature, which is the combination of the function
- name and its sequence of argument types. When compiled as a C++ program, the
- output of Listing 5 and Listing 6 from Borland C++ is
- Error: Undefined symbol f(int) in module safe1.cpp
- Most compilers achieve this type-safe linkage by encoding the function's
- signature along with its name, a technique often referred to as function name
- encoding, name decorating, or my favorite, name mangling. For example, the
- function f(int) might appear to the linker as
- f__Fi // f is a function
- // taking an int
- but f(double) would be
- f__Fd // f is a function
- // taking a double
- Since the names are different, the linker can't find f(int) in this example,
- and reports an error.
-
-
- References
-
-
- Since C passes function parameters by value, passing large structures to
- functions can waste a lot of time and stack space. The typical work-around is
- to pass a pointer. For example, if struct Foo is a large record structure, you
- can do something like the following:
- void f(struct Foo *fp)
- {
- /* Access the structure
- through fp */
- fp->x=...
- etc.
- }
- You have to pass the address of a struct Foo in order to use this function, of
- course:
- struct Foo a;
- ...
- f(&a);
- The C++ reference mechanism is a notational convenience that saves you the
- bother of providing explicit indirection of pointer variables. In C++, you can
- render the above code as:
- void f(Foo &fr)
- {
- /* Access members directly */
- fr.x = ...
- etc.
- }
- You can now call f without using the address-of operator, like this:
- Foo a;
- ...
- f(a);
- The ampersand in the prototype for f instructs the compiler to pass its
- argument by reference, which in effect takes care of all the indirection for
- you. For you Pascal programmers, reference parameters are equivalent to VAR
- parameters.
- Call-by-reference means that any changes you make to a function parameter
- affect the original argument in the calling program. Thus, you can write a
- swap function (not a macro) that actually works (see Listing 7). If you don't
- plan on modifying a reference argument, declare it a reference-to-const, like
- I did in Listing 4. A reference-to-const argument has the safety and
- notational convenience of call-by-value, and the efficiency of
- call-by-reference.
- As Listing 8 illustrates, you can also return an object from a function by
- reference. It may look strange to have a function call on the left-hand side
- of an assignment, but this comes in handy when overloading operators
- (especially operator= and operator[] when used as member functions).
-
-
- Type-Safe I/O
-
-
- I'm sure every C programmer has been bitten by using incorrect format
- descriptors in printf. printf has no way to check if the data items you pass
- it match your format string. How often have you done something like the
- following, only to discover the problem at run time:
- double d;
- ...
- printf("%d\n",d); /* should've used
- %f */
- The C++ IOStreams library, on the other hand, uses the object's type to
- determine the proper formatting:
- double d;
- ...
- cout << d << endl; // can't fail
- There is no way for the output stream to misinterpret your value, If you want
- to print floating-point numbers with a fixed precision, you can say so just
- once:
- double x = 1.5, y = 2.5;
- cout.precision(2); // Show 2 decimals
- cout.setf(ios::showpoint); // Preserve trailing 0's
-
- cout << x << endl; // prints 1.50
- cout << y << endl; // prints 2.50
- The token endl is a manipulator, a special object you insert into a stream to
- create a side effect -- in this case, to start a new line and flush the output
- buffer. For more information on IOstreams, see the Code Capsule in the July
- 1993 issue of CUJ.
-
-
- Function Overloading and Templates
-
-
- The swap function in Listing 7 is useful only if you want to swap integers.
- What if you want to swap two objects of any built-in type? C++ allows you to
- define multiple functions of the same name, as long as their signatures are
- different. Therefore you can define a swap for all built-in types:
- void swap(char &, char &);
- void swap(int &, int &);
- void swap(long &, long &);
- void swap(float &, float &);
- void swap(double &, double &);
- etc.
- You can then call swap for any two objects of the same built-in type. If you
- were to implement each of these functions, however, before long you would
- discover that you were doing the same thing over and over -- the only thing
- that changes is the type of the objects you want to swap. To save tedium and
- the chance of making a silly mistake, you can define a function template
- instead. As Listing 9 demonstrates, you preface the function with the phrase
- template<class T>
- which says that in the code that follows, the token T stands for an arbitrary
- data type, either built-in or user-defined. You then replace all occurrences
- of the data type of the objects to be swapped with the template parameter T.
- When the compiler sees a call to swap, it instantiates the appropriate
- function, inferring the type from the type of the operands. In other words,
- when the compiler sees
- swap(i ,j);
- it actually generates the code for swap(int &, int &), as if you had created
- it yourself (but without human error).
-
-
- Operator Overloading
-
-
- You can also overload operators in C++. For example, suppose you define a
- complex number data type as:
- struct complex
- {
- double real, imag;
- };
- It would be quite convenient if you could use infix notation for adding
- complex numbers, such as:
- complex c1, c2;
- ...
- complex c3 = c1 + c2;
- Operator overloading enables you to do just this. When the compiler encounters
- an expression such as c1 + c2, it looks for one of the following two
- functions:
- operator+(const complex &, const complex &);
- complex::operator+(const complex &);
- The operator keyword is part of the function name. You could define a global
- operator+ for adding two complex numbers like this:
- complex operator+(const complex &c1, const complex &c2)
- {
- complex r;
- r.real = c1.real + c2.real;
- r.imag = c1.imag + c2.imag;
- return r;
- }
- The compiler will not allow you to overload built-in operations, such as
- addition of two ints, therefore at least one of the operands must be of a
- user-defined type.
- The IOStreams library uses operator overloading to determine how to format the
- various built-in types. For example, the ostream class, of which cout is an
- instance, overloads operator<< for all the built-in types. When the compiler
- sees the expression
- cout << i; // where i is an int
- it generates the following function invocation
- cout.operator<<(i);
- which formats the number correctly.
- Listing 10 shows how to extend IOStreams by overloading operator<< for complex
- numbers (sample output in Listing 11). The compiler transforms the expression
- cout << c // c is a complex number
- into the function call
- operator<<(cout, c)
- which invokes operator<<(ostream&, const complex&). This function in turn
- breaks the operation down into formatting objects of built-in types. This
- function also returns the stream so that you can chain multiple stream
- insertions in a single statement. For example, the expression
- cout << c1 << c2
- becomes
- operator<<(operator<<(cout,c1), c2)
-
- which requires that operator<<(ostream&, const complex&) return the stream.
- The operator returns the stream by reference for efficiency.
-
-
- Inline Functions
-
-
- The inline keyword, seen in Listing 10, tells the compiler that you want the
- code "inlined" for efficiency. Each call to an inline function inserts the
- appropriate code inline, avoiding the usual overhead of an actual function
- call. This mechanism is different from a function-like macro, which performs
- text substitution before program translation. Inline functions have all the
- type checking and semantics of true functions, without the sensitivity to side
- effects that macros have. For example, you might define a macro to find the
- smaller of two numbers as follows:
- #define min(x,y) ((x) < (y) ? (x) : (y))
- which fails miserably with an incremented argument, such as
- min(x++,y++)
- Inline functions don't have this problem, since they behave like real
- functions.
- Not all functions can or should be inlined, however. Certainly a recursive
- function doesn't qualify for inlining. Large functions can increase code size
- substantially when inlined. Inlining is mainly for small, simple functions.
-
-
- Default Arguments
-
-
- Default arguments allow a function to infer values from its prototype. The
- program in Listing 12 has a function with the prototype:
- int minutes(int hrs, int min = 0);
- The "= 0" after the last parameter instructs the compiler to supply the value
- 0 for the second argument when you omit it in a program. This mechanism is
- essentially a shorthand for defining related overloaded functions. In this
- case, it is a short cut for the following:
- int minutes(int hrs, int min);
- int minutes(int hrs); // ignores minutes
- The complex constructor in Listing 10 uses default arguments to allow you to
- define a complex number with 0, 1, or 2 arguments, as in
- complex c1; // (0,0)
- complex c2(1); // (1,0)
- complex c3(2,3) // (2,3)
- The third form is the one used in the return statement of operator+ in Listing
- 10.
-
-
- new and delete
-
-
- To use the heap in C, you need to compute the size of the object you want to
- create:
- struct Foo *fp = malloc(sizeof(struct Foo));
- In C++,the new operator computes the size of an object for you:
- Foo *fp = new Foo;
- To allocate an array in C, you call a different function:
- struct Foo *fpa = calloc(n,sizeof(struct Foo));
- In C++, new knows about arrays:
- Foo *fpa = new Foo[n];
- In addition, the new operator automatically invokes the appropriate
- constructor to initialize the object(s) before it returns you the pointer. For
- example, creating complex numbers on the heap automatically initializes them,
- as in
- complex *cp1 = new complex; // -> (0,0)
- complex *cp2 = new complex(1); // -> (1,0)
- complex *cp3 = new complex(2,3); // -> (2,3)
- To return dynamic memory to the heap, you use the delete operator, which comes
- in two forms. For singleton objects you use this one:
- delete fp;
- delete cp1;
- but deleting arrays requires the second form, with the following syntax:
- delete [] fpa; // array-delete syntax
- Like other C++ features, new and delete improve the type safety of your
- programs -- you aren't just asking for an amount of memory, you are requesting
- objects, with the appropriate type-checking and initialization.
-
-
- Declaration Statements
-
-
- In C++, a declaration can appear anywhere a statement can. Instead of having
- to group declarations at the beginning of a block, you can declare objects at
- their point of first use. For example, in Listing 13 the array a is visible
- throughout the function body, but n is not valid until its declaration, and i
- not until the next line. With current compilers all three objects persist
- until the end of the block, which is why i is not redeclared in the second
- for-loop. The C++ standard now states, however, that the scope of variables
- declared in a control loop is the loop itself. In the future, therefore, you
- will have to redeclare i in the second loop, as in
- for (int i = n-1; i >= 0; --i)
-
-
-
- Static Initialization
-
-
- In C, variables of static storage duration (those declared at file scope or
- with the static keyword) are initialized at program startup and remain active
- throughout program execution. Since such objects can only use constant
- expressions as initializers, this isn't a challenge for compiler writers to
- implement. Because objects in C++ usually require constructors, however,
- static objects must be able to call functions in their initialization. For
- this reason, C++ allows statements at file scope such as
- double x = sqrt(5);
-
-
- C Compatibility
-
-
- To accommodate strong type-checking and object-orientation, C++ has had to
- part ways with C on a few language issues. If you are going to use C++ as a
- better C, you should be aware of those features that behave differently in the
- two languages.
- First of all, C++ has more keywords than C. You must avoid using any of the
- tokens in Table 1 as identifiers in your programs.
- You can use const integer objects and enumerated constants in array
- declarations in C++, as in
- const int SIZE = 100;
- enum {BIGGER = 1000};
- int a[SIZE], b[BIGGER];
- Global const declarations have internal linkage by default, whereas in C they
- have external linkage. As a result, you can use const definitions at file
- scope in C++, in place of #define macros in header files. If you want a const
- object to have external linkage, you must use the extern keyword.
- In C, you can assign a pointer to any type to and from a void *. This allows
- you to use malloc without a cast, as in
- #include <stdlib.h>
- ...
- char *p = malloc(strlen*s) + 1);
- The C++ type system will not allow you to assign from a void pointer without a
- cast. For the example above, you should use the new operator anyway.
- If you omit arguments in a function definition in C, the compiler does not
- check how you use that function (i.e., you can pass any number and type of
- arguments to it). In C++, the prototype f() is equivalent to f(void). If you
- insist upon the unsafe C behavior, use f(...).
- And finally, single-quoted character constants are of type char in C++, not
- int. Otherwise, the expression
- cout << 'a'
- would print the internal character code (e.g., 97 for ASCII) instead of the
- letter a.
-
-
- Summary
-
-
- You might think that the key distinction between C and C++ is support for
- object-oriented programming, but even more crucial is the issue of type
- safety. Not all programs are object-oriented programs. Even if you don't use
- classes and other object-oriented mechanisms, using C++ as a type-safe C will
- help make you a safer programmer.
- Table 1 C++ Keywords and Reserved Words (as of November 1994)
- and false signed
- and_eq float sizeof
- asm for static
- auto friend static_cast
- bitand goto struct
- bitor if switch
- bool inline template
- break int this
- case long throw
- catch mutable true
- char namespace try
- class new typedef
- compl not typeid
- const_cast not_eq union
- continue operator unsigned
- default or using
- delete or_eq virtual
- do private void
- double protected volatile
- dynamic_cast public wchar_t
- else register while
- enum reinterpret_cast xor
- explicit return xor_eq
- extern short
-
- Listing 1 Illustrates the need for function prototypes
-
- /* convert1.c */
- #include <stdio.h>
-
- main()
- {
- dprint(123);
- dprint(123.0);
- return 0;
- }
-
- dprint(d)
- double d;
- {
- printf("%f\n",d);
- }
-
- /* Output:
- 0.000000
- 123.000000
- */
-
- /* End of File */
-
-
- Listing 2 Illustrates automatic conversion via function prototypes
- /* convert2.c */
- #include <stdio.h>
-
- void dprint(double);
-
- main()
- {
- dprint(123);
- dprint(123.0);
- return 0;
- }
-
- void dprint(double d)
- {
- printf("%f\n",d);
- }
-
- /* Output:
- 123.000000
- 123.000000
- */
-
- /* End of File */
-
-
- Listing 3 Illustrates an implicit user-defined conversion
- // convert3.cpp
- #include <iostream. h>
-
- struct A
- {
- double x;
-
- A(double d)
-
- {
- cout << "A::A(double)" << endl;
- x = d;
- }
- };
-
- void f(const A& a)
- {
- cout << "f: "<< a.x << endl;
- }
-
- main()
- {
- A a(1);
- f(a);
- f(2);
- return 0;
- }
-
- // Output:
- A::A(double)
- f: 1
- A::A(double)
- f:2
-
- // End of File
-
-
- Listing 4 Shows that only one user-defined conversion is allowed
- // convert4.cpp
- #include <iostream.h>
-
- struct B;
-
- struct A
- {
- double x;
-
- A(const B& b);
- };
-
- void f(const A& a)
- {
- cout << "f: "<< a.x << endl;
- }
-
- struct B
- {
- double y;
-
- B(double d) : y(d)
- {
- cout << "B::B(double)" << endl;
- }
- };
-
- A::A(const B& b) : x(b.y)
- {
- cout << "A::A(const B&)" << endl;
-
- }
-
- main()
- {
- A a(1);
- f(a);
-
- B b(2);
- f(b);
-
- // The following won't compile:
- // f(3)
-
- // But these will:
- f(B(3));
- f(A(4));
- return 0;
- }
-
- // Output:
- B::B(double)
- A::A(const B&)
- f: 1
- B::B(double)
- A::A(const B&)
- f: 2
- B::B(double)
- A::A(const B&)
- f: 3
- B::B(double)
- A::A(const B&)
- f: 4
- // End of File
-
-
- Listing 5 Illustrates program linkage (see also Listing 6)
- void f(int);
-
- main()
- {
- f(1);
- return 0;
- }
-
- /* End of File */
-
-
- Listing 6 A function intended to link with listing 5
- #include <stdio.h>
-
- void f(double x)
- {
- printf("f: %f\n",x);
- }
-
- /* End of File */
-
-
- Listing 7 A swap function that illustrates call-by-reference
-
- //swap.cpp
- #include <stdio.h>
-
- void swap(int &, int &);
-
- main()
- {
- int i = 1, j = 2;
-
- swap(i, j );
- printf("i == %d, j == %d\n",i,j);
- return 0;
- }
-
- void swap(int &x, int &y)
- {
- int temp = x;
- x = y;
- y = temp;
- }
-
- // Output:
- i == 2, j == 1
-
- // End of File
-
-
- Listing 8 Returns an object from a function by reference
- //retref.cpp: Returning a reference
- #include <stdio.h>
-
- int & current(); // Returns a reference
-
- int a[4] = {0,1,2,3};
- int index = 0;
-
- main()
- {
- current() = 10;
- index = 3;
- current() = 20;
- for (int i = 0; i < 4; ++i)
- printf("%d ", a[i]);
- putchar( '\n');
- return 0;
- }
-
- int & current()
- {
- return a[index];
- }
-
- // Output:
- 10 1 2 20
-
- // End of File
-
-
- Listing 9 A function template for swap()
-
- //swap2.c
- #include <iostream.h>
- #include "complex.h"
-
- template<class T>
- void swap(T &x, T &y)
- {
- T temp = x;
- x = y;
- y = temp;
- }
-
- main()
- {
- int i = 1, j = 2;
- swap(i,j);
- cout << "i == " << i << ", j == " << j << endl;
-
- double x = 3.0, y = 4.0;
- swap(x,y);
- cout << "x == " << x << ", y == " << y << endl;
-
- char *s = "hi", *t = "there";
- swap(s,t);
- cout << "s == " << s << ", t == " << t << endl;
-
- complex c1(5,6), c2(7,8);
- swap(c1,c2);
- cout << "c1 == " << c1 << ", c2 == " << c2 << edl;
- return 0;
- }
-
- // Output:
- i == 2, j == 1
- x == 4, y == 3
- s == there, t == hi
- c1 == (7,8), c2 == (5,6)
-
- /* End of File */
-
-
- Listing 10 operator+ and operator<< for a complex number data type
- #include <iostream.h>
-
- struct complex
- {
- double real, imag;
-
- complex(double = 0.0, double = 0.0);
- };
-
- complex::complex(double r, double i)
- {
- real = r;
- imag = i;
- }
-
- inline ostream& operator<<(ostream &os, const complex &c)
- {
-
- os << '(' << c.real << ',' << c,imag << ')';
- return os;
- }
-
- inline complex operator+(const complex &c1,
- const complex &c2)
- {
- return complex(c1.real+c2.real,c1.imag+c2.imag);
- }
-
- /* End of File */
-
-
- Listing 11 Uses the complex number data type
- #include <iostream.h>
- #include "complex.h"
-
- main()
- {
- complex c1(1,2), c2(3,4);
-
- cout << c1 << "+" << c2 <<" == "<< c1+c2 << endl;
- return 0;
- }
-
- // Output:
- (1,2) + (3,4) == (4,6)
-
- /* End of File */
-
-
- Listing 12 Illustrates default arguments
- // minutes.cpp
-
- #include <iostream.h>
-
- inline int minutes(int hrs, int mins = 0)
- {
- return hrs * 60 + mins;
- }
-
- main()
- {
- cout << "3 hrs == " << minutes(3) <<" minutes" << endl;
- cout << "3 hrs, 26 min ==" << minutes(3,26) <<" minutes" <<
- endl;
- return 0;
- }
-
- // Output:
- 3 hrs == 180 minutes
- 3 hrs, 26 min == 206 minutes
-
- // End of File
-
-
- Listing 13 Shows that declarations are statements
- // declare.cpp
- #include <iostream.h>
-
-
- main()
- {
- int a[] = {0,1,2,3,4};
-
- // Print address and size
- cout << "a == " << (void *) a << endl;
- cout << "sizeof(a) == "<< sizeof(a) << endl;
-
- // Print forwards
- size_t n = sizeof a / sizeof a[0];
- for (int i = 0; i < n; ++i)
- cout << a[i] << ' ';
- cout << endl;
-
- // Then backwards
- for (i = n-1; i >= 0; --i)
- cout << a[i] << ' ';
- cout << endl;
- return 0;
- }
-
- // Output:
- a == 0xffec
- sizeof(a) == 10
- 0 1 2 3 4
- 4 3 2 1 0
-
- // End of File
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Stepping Up To C++
-
-
- Mutable Class Members
-
-
-
-
- Dan Saks
-
-
- Dan Saks is the president of Saks & Associates, which offers consulting and
- training in C++ and C. He is secretary of the ANSI and ISO C++ committees. Dan
- is coauthor of C++ Programming Guidelines, and codeveloper of the Plum Hall
- Validation Suite for C++ (both with Thomas Plum). You can reach him at 393
- Leander Dr., Springfield OH, 45504-4906, by phone at (513)324-3601, or
- electronically at dsaks@wittenberg.edu.
-
-
- For the past three months, I've been listing ways that the programming
- language described by the C++ Draft Standard (as of Fall, 1994) differs from
- the language described in the Annotated Reference Manual (ARM [1]). Thus far,
- I've described the major extensions (see "Stepping Up to C++: C++ at CD
- Registration," CUJ, January 1995.) I've also described numerous minor
- enhancements (see "Stepping Up to C++: Minor Enhancements to C++ as of CD
- Registration" and "...More Minor Enhancements to C++ as of CD Registration,"
- CUJ, February and March, 1995.)
- I have a few minor enhancements left to go. This month I present one more such
- enhancement, mutable class members. Not that mutable members are very
- important in themselves, but giving the background necessary to explain them
- raises a lot of interesting design and language issues along the way, so I
- think it's worth the time.
- Mutable class members provide added support for logically-const class objects.
- Understanding mutable class members requires an understanding of the const
- qualifier, logical vs. physical const-ness, const member functions, and the
- "casting away" of const. Those of you already familiar with these terms can
- skip directly to the discussion of lazy evaluation, which presents a problem
- solved by mutable class members. If you skip too far ahead, you can always
- skip back.
-
-
- The const Qualfier
-
-
- C++ introduced the const qualifier as a way to specify read-only objects. When
- applied to an object, const means the program may inspect but not change the
- value of that object. A C++ translator is therefore free to place
- const-qualified objects in read-only memory (ROM). In an environment that
- supports multitasking with shared memory, a translator can place const objects
- in a sharable, read-only data segment (which is just another flavor of ROM).
- In some cases, a translator can even optimize a const object out of existence.
- For example, given
- const int MAX = 100;
- a C++ translator (with the appropriate compiler and linker options) may place
- MAX into ROM. Moreover, if the program never takes the address of, nor binds a
- reference to MAX, a compiler need not generate storage for it. The compiler
- can simply "optimize away" MAX by using its value (100) as an immediate
- operand in generated machine instructions.
- C++ erects various barriers to prevent programs from accidentally modifying
- const objects. For example, a C++ compiler must diagnose every attempt to
- modify a const-qualified object, such as
- MAX = 99; // error
- or
- ++MAX; // error
- Furthermore, the pointer and reference conversion rules won't let you strip
- off const qualifiers (at least not without casts). For example,
- int *pi = &MAX; // error
- is an error because it attempts to convert a const int * (the type of &MAX) to
- an int * (the type of pi), losing a const qualifier in the process. Failure to
- catch this error would permit an otherwise valid assignment like
- *pi = 99;
- to write into a const object. Of course, you can strip a const qualifier using
- a cast, such as
- int *pi = (int *)&MAX;
- or the new-style
- int *pi = const_cast<int *>(&MAX);
- if you think you know what you're doing. A cast expression that strips away a
- const qualifier is said to "cast away const."
- When applied to an aggregate, such as an array or struct, the const qualifier
- actually percolates down to the elements of the aggregate. That is, the
- elements of a const array are themselves const objects, as are the members of
- a const object of a struct type. Thus, given
- const char name[] = "jem";
- an assignment such as
- name[0] = "J";
- is an error because it tries to alter the value of name[0], which is an object
- of type const char.
-
-
- Logically const vs. Physically const
-
-
- C++ actually supports different degrees of const-ness. Some objects defined
- const may be physically const, that is, residing in ROM and therefore
- absolutely immutable. Other objects declared const are merely logically const.
- A logically const object can't be modified in the current context, but it may
- be modifiable in some other context.
- For example, the standard header <string.h> declares the strlen function as
- size_t strlen(const char *s);
- Inside the body of strlen, *s designates a logically-const object of type
- char. strlen can modify s, but it can't modify *s (any character addressed by
- s). On some calls to strlen, s might point to an actual argument that's a
- physically-const object. On other calls, it might point to a non-const object.
- For example, during the call strlen(name), where name is as defined earlier, s
- points to characters that are both logically and physically const. But during
- the call strlen(title), where title is
- char title[] = "Fing";
- s is a pointer to logically-const characters that are actually non-const.
- strlen can't change the characters in title, but other parts of the program
- might.
- In general, I recommend using const as often as appropriate in declarations.
- Specifically, I suggest declaring every logically- or physically-const object
- as such by using the const qualifier explicitly. Any purportedly
- general-purpose library must obey this rule, or it won't be very useful in
- ROM-based applications.
- For instance, the standard strcmp function is declared
-
- int strcmp
- (const char *s1, const char *s2);
- strcmp compares two strings without altering them. Therefore, inside strcmp,
- both s1 and s2 point to logically-const strings, and the declaration says so
- explicitly. If either const were missing, an application could not use this
- function to compare two const strings. On the other hand, a function such as
- char *strcpy(char *s1, const char *s2);
- can only declare its second parameter as const. strcpy must reserve the right
- to alter the string addressed by s1, otherwise it can't do its job of copying
- s2 to s1.
- Even if you don't write ROM-based applications, you should still use the const
- qualifier generously. Using the const qualifier builds more static type
- information into programs. The compiler can use this information to detect
- logic errors. As always, the more you tell the compiler about your intent, the
- more easily it can tell when your program violates that intent. Code that
- consistently enforces logical constness without casting away const is said to
- be "const correct."
-
-
- const Member Functions
-
-
- Indeed, writing const-correct classes requires extra effort. For instance,
- consider the array class template sketched in Listing 1. (This is a
- templatized version of the float_array class I used for my examples in
- "Stepping Up to C++: Dynamic Arrays," CUJ, November, 1992.) Let's see what's
- required to make it const correct.
- Listing 1 includes a non-member function template, sigma, that returns the sum
- of the elements in an array<T>. The function declaration:
- template <class T>
- T sigma(array<T> &a)
- suggests by the absence of any const qualifiers that sigma might change its
- actual argument in the course of summing the elements. But logically, sigma
- shouldn't change its argument, and nothing in the function body seems to
- suggest that it does. Therefore, sigma's parameter should be const, as in
- template <class T>
- T sigma(const array<T> &a)
- so the compiler can enforce the logical const-ness of array a.
- In effect, the const qualifier in the declaration is sigma's promise that it
- won't change the actual array<T> object referenced by a. The compiler then
- backs that promise by rejecting any statement in sigma's body that tries to
- change a. In particular, since every member of a const object is itself const,
- the compiler rejects any statement that tries to change a member of a. This
- includes any attempt to pass a, or a member of a, as a non-const argument to
- another function.
- Recompiling the code after adding the const qualifier to sigma's parameter
- list triggers a number of compile-time errors inside sigma. For one, the
- compiler complains about the call to a.length() in
- for (size_t i = 0; i < a.length(); ++i)
- sum += a[i];
- The problem is, now that a is const, sigma can't call a member function
- applied to a unless it can be sure that the member function won't alter a. How
- can you tell the compiler that calling a. length() won't alter a? By declaring
- length as a const member function:
- size_t length() const;
- In effect, a const member function promises to treat the object to which it
- applies as a const object. Once you change length to a const member function,
- sigma has no problem calling it.
- The keyword const after the parameter list actually modifies the object
- addressed by the implicit this parameter. A non-const member function for a
- class X implicity declares this as
- X *const this;
- That is, this is a non-modifiable pointer to a modifiable object. In a const
- member function, the implicit declaration for this is:
- const X *const this;
- or equivalently:
- X const *const this;
- In other words, this is a non-modifiable pointer to a non-modifiable object.
- You can apply a const member function to a non-const object. The function
- simply treats the non-const object as const. However, you cannot apply a
- non-const member function to a const object, because the non-const member
- function might try to change the const object.
- Adding the const qualifier to sigma's parameter list also causes a
- compile-time error on the call a[i] inside sigma. Again, the problem is that
- array<T>::operator[](size_t) is a non-const member function. Rather than
- simply change operator[] to a const member function, a better solution is to
- overload operator[] as both const and non-const member functions:
- const T &operator[](size_t i) const;
- T &operator[](size_t i);
- Hence, the expression a[i] invokes the const operator[] if a is a const array,
- and invokes the non-const operator[] if a is non-const. Both forms of
- operator[] return a reference to the selected array element, but the const
- form returns a reference to a const array element, while the non-const form
- returns a reference to a non-const element. The const-corrected array class
- appears in Listing 2. (For more about overloading operator[] as both const and
- non-const member functions, see "Stepping Up to C++: operator[]," CUJ,
- January, 1993, or Meyers [2].)
-
-
- Lazy Evaluation
-
-
- Without a doubt, the primary benefit of C++ classes is that they support data
- abstraction. A well-written class defines an abstract type with complete and
- consistent behavior that you can use with little or no concern for its
- underlying data representation. It hides the data representation as private
- data members, and grants restricted access to that data through public member
- functions.
- Most classes have attributes that you can represent in more than one way. For
- a particular class attribute, you might simply store a value representing the
- attribute in a data member. Or, you might be able to compute the attribute
- from other data in the object's representation.
- For example, complex numbers have two common representations:
- 1. in rectangular form, (r, i), where r is the real part and i is the
- imaginary part
- 2. in polar form, (rho, theta), where rho is the magnitude (distance from the
- origin), and theta is the phase angle (typically in radians)
- These two forms are related by the following equations:
- rho = sqrt(re * re + im * im);
- theta = arctan(im/re);
- If your application for complex numbers uses both forms at one time or
- another, you might design a complex number class that stores both
- representations:
- class complex
- {
- public:
- // ...
- private:
- double re, im;
- double rho, theta;
-
- };
- This design is certainly straightforward, but it has at least two drawbacks:
- 1. It doubles the storage requirements for every complex number.
- 2. Every arithmetic operation on complex numbers, such as + or *, must compute
- the results twice -- once for each form.
- As an alternative, your complex class can store just one form, and compute the
- other form on demand, as shown in Listing 3. The complex class in Listing 3
- stores only the rectangular form, and recomputes the polar form on demand. For
- instance, calling z1. real() simply returns the value of the private data
- member re, which stores the real part. Calling z1.theta() computes the angle
- using re and im, the imaginary part.
- Note that the four member functions, real, imag, rho, and theta, are const
- member functions. And well they should be. Indeed, none of them changes the
- value of the complex number. I see no reason to prevent a program from
- requesting these values of a const complex object.
- The problem with computing the polar form on demand is that some applications
- might calculate the rho and theta for the same complex numbers over and over
- again. Granted, the computation is not that complicated, but it uses the sqrt
- and atan2 functions which are more than just a few instructions on most
- architectures. If these recalculations prove to be too expensive, a different
- design for complex numbers might be appropriate.
- Listing 4 shows a complex number class that caches the polar form in a
- dynamically-allocated auxiliary structure. Each complex number has three
- private data members:
- class complex
- {
- //...
- private:
- double re, im;
- struct polar;
- polar *p;
- };
- As before, re and im hold the real and imaginary parts. p is a a pointer to an
- auxiliary structure of type polar. polar is a forward-declared nested type,
- defined simply as
- struct complex::polar
- {
- double rho, theta;
- };
- That is, it holds the polar representation of a complex number.
- By this design, all complex numbers start out by storing only the rectangular
- form (in re and im). The constructors always store 0 (a null pointer) into
- member p. Most arithmetic operations, such as operator+ in Listing 4, use the
- rectangular forms of the operands, and return a result in rectangular form.
- Since operator+ uses a constructor to build the result, that constructor sets
- the pointer in the result to null.
- Like its previous version (in Listing 3), this complex class never computes
- the polar form until needed. However, unlike before, this implementation
- doesn't just discard the result and calculate again when asked. Rather, it
- dynamically allocates a complex::polar object, and caches the result in that
- object. If the value of the complex object doesn't change, the class satisfies
- repeated requests for the polar coordinates by reading them from the cache,
- which is much faster than recalculating.
- This caching technique is an example of a more general technique known in some
- circles as "lazy evaluation." The basic philosophy of lazy evaluation is
- "Don't do it unless you have to, and then don't do it again." This technique
- is most useful for any class that has an attribute where
- accessing the attribute's value incurs a high cost in time or space or both,
- and
- typical applications request the attribute's value from relatively few
- objects.
- Meyers [2], Murray [3], and Plum and Saks [4] offer other examples using lazy
- evaluation (although none uses that term). Meyers uses lazy evaluation to
- postpone computing the length of string objects. Murray presents a variation
- on the complex number class. Plum and I sketch a class for tokens (such as in
- a parser) that caches each token's hash value during symbol lookup.
-
-
- Casting Away const, Again
-
-
- As in the earlier complex class (Listing 3), the four member functions, real,
- imag, rho, and theta of the later class (Listing 4) are also const member
- functions. Programmers using complex numbers should quite reasonably expect to
- be able to obtain any of these values from a const complex object, regardless
- of the implementation. There's no problem declaring real and imag in Listing 4
- as const; they are identical to the corresponding functions in Listing 3.
- However, the rho and theta functions in Listing 4 are a little tricky and
- require a careful look.
- The problem with the rho and theta functions is that even though they don't
- change the value of a complex object as seen outside the class, they might
- change the private data of that object. Specifically, if pointer p is null,
- then rho or theta must change p to point to a newly-allocated polar object,
- using something of the form:
- if (p == 0)
- p = new polar(..., ...);
- However, in a const member function of class complex, this points to a const
- complex object, meaning that re, im and p are themselves const. Thus, the
- assignment to p as written above is an error.
- Of course, you can always change rho and theta to non-const member functions.
- But then you won't be able to call rho and theta for a const complex object.
- Not good. Alternatively, you can leave rho and theta as const member functions
- and cast away const from the complex object inside each function, as shown in
- Listing 4.
- There are various ways to cast away const inside a const member function. rho
- and theta each illustrate a different style. rho uses the following approach:
- complex *This =
- const_cast<complex *>(this);
- This->p = new polar(..., ...);
- This is local variable which points to the same object as this. However, This
- is declared without const, so rho can use it to treat the object as non-const.
- Copying this to This loses a const qualifer, so the conversion requires a cast
- to sneak past the compiler. I used the new-style cast. On compilers that don't
- yet support new-style casts, use
- complex *This = (complex *)this;
- theta casts away const a little differently. It simply applies a cast to p
- itself:
- (polar *&)p = new polar(..., ..);
- This casts p to a reference to a (non-const) pointer to polar. Writing the
- cast as just (polar *)p converts p to the correct type, but the cast yields an
- rvalue. An rvalue expression cannot appear on the left of an assignment.
- Casting to a reference yields an lvalue, which can appear to the left side of
- an assignment. The reference yields the same result as the slightly
- longer-winded pointer cast, *(polar **)&p.
- By the way, Listing 4 uses only two relatively new C++ features: the
- forward-declaration of nested class complex::polar and the new-style cast in
- complex::rho. I found two compilers that will compile and execute Listing 4 as
- written, namely Borland C++ 4.5 for DOS/Windows, and MetaWare High C++ 3.3 for
- Extended DOS using the Phar Lap 386 DOS Extender 4.1. If you move the
- definition for complex::polar back inside the definition for complex, and
- change the new-style cast to an old-style cast, most other compilers should
- accept it.
-
-
- At last, Mutable Class Members
-
-
- If using casts, as I did above, makes you queasy, that's good. Casts can be
- pretty dangerous, and you should use them sparingly and with great care. In
- this case, how can you know that it's safe to cast away const from a complex
- number? Functions such as rho and theta are indeed logically const, but they
- assume the object is not physically const (it's not in ROM). If the complex
- object is really physically const, writing into the object causes undefined
- behavior.
- Mutable class members offer a way to implement logically const operations
- without casting away const. A mutable member is one that is never const, even
- if it is a member of an object declared const. For example, in
- class complex
- {
- public:
-
- // ....
- private:
- double re, im;
- struct polar;
- mutable polar *p;
- };
- Here, p is always a non-const object, even in complex objects declared const.
- Thus, const member functions like rho and theta need not cast away const in
- order to write to p. Listing 5 shows the changes to the complex class that
- result from using a mutable member instead of casts. So far, I've found only
- one compiler that compiles and executes this code -- the MetaWare/Phar Lap
- combination.
- Syntactically, mutable is a storage class specifier, like auto, register, or
- static. It can only appear in declarations of class data members. You cannot
- declare a data member both mutable and const. For example,
- class X
- {
- mutable const int *p; // ok
- mutable int *const q; // error
- };
- The declaration for p is okay because (ignoring the mutable specifier for the
- moment) p is a non-const pointer to a const int. Adding mutable to the
- declaration specifies that p will always be non-const, even in a const X
- object.
- The declaration for q is an error because (ignoring the mutable specifier for
- the moment) q is a const pointer to a non-const int. That is, q itself is
- declared const. Adding the mutable specifier to the declaration conflicts with
- the const already applied to q, hence the error.
- Next month... the remaining minor enhancements.
- References
- [1] Margaret A. Ellis and Bjarne Stroustrup. The Annotated C++ Reference
- Manual. (Addison-Wesley, 1990).
- [2] Scott Meyers. Effective C++. (Addison-Wesley, 1992).
- [3] Robert B. Murray. C++ Strategies and Tactics. (Addison-Wesley, 1993).
- [4] Thomas Plum and Dan Saks. C++ Programming Guidelines. (Plum Hall, 1991).
-
- Listing 1 A simple array class template
- // array1.cpp
-
- #include <stddef.h>
-
- template <class T>
- class array
- {
- public:
- array(size_t n = 0);
- array(const array &a);
- ~array();
- array &operator=(const array &a);
- T &operator[](size_t i);
- size_t length();
- private:
- T *pa;
- size_t len;
- };
-
- // ...
-
- template <class T>
- T &array<T>::operator[](size_t i)
- {
- return pa[i];
- }
-
- template <class T>
- inline size_t array<T>::length()
- {
- return len;
- }
-
- ...
-
- template <class T>
-
- T sigma(array<T> &a)
- {
- T sum = 0;
- for (size_t i = 0; i < a.length(); ++i)
- sum += a[i];
- return sum;
- }
- // End of File
-
-
- Listing 2 A simple const-correct array class template
- // array2.cpp
-
- #include <stddef.h>
-
- template <class T>
- class array
- {
- public:
- array(size_t n = 0);
- array(const array &a);
- ~array();
- array &operator=(const array &a);
- const T &operator[](size_t i) const;
- T &operator[](size_t i);
- size_t length() const;
- private:
- T *pa;
- size_t len;
- };
-
- // ...
-
- template <class T>
- const T &array<T>::operator[](size_t i) const
- {
- return pa[i];
- }
-
- template <class T>
- T &array<T>::operator[](size_t i)
- {
- return pa[i];
- }
-
- template <class T>
- inline size_t array<T>::length() const
- {
- return len;
- }
-
- ...
-
- template <class T>
- T sigma(const array<T> &a)
- {
- T sum = 0;
- for (size_t i = 0; i < a.length(); ++i)
- sum += a[i];
-
- return sum;
- }
- // End of File
-
-
- Listing 3 A rudimentary class for complex numbers which recomputes the polar
- form on demand
- // z1.cpp
-
- #include <iostream.h>
- #include <iomanip.h>
- #include <math.h>
-
- class complex
- {
- public:
- complex(double r, double i);
- double real() const;
- double imag() const;
- double rho() const;
- double theta() const;
- private:
- double re, im;
- };
-
- inline complex::complex(double r, double i)
- : re(r), im(i)
- {
- }
-
- inline double complex::real() const
- {
- return re;
- }
-
- inline double complex::imag() const
- {
- return im;
- }
-
- inline double complex::rho() const
- {
- return sqrt(re * re + im * im);
- }
-
- inline double complex::theta() const
- {
- return atan2(im, re);
- }
-
- complex operator+(const complex &z1, const complex &z2)
- {
- return complex
- (z1.real() + z2.real(), z1.imag() + z2.imag());
- }
-
- int main()
- {
- complex z1(3, 4);
- cout << '(' << z1.real() << ',' << z1.imag() << ')'
-
- << endl;
- cout << '(' << z1.rho() << ',' << z1.theta() << ')'
- << endl;
- complex z2(1, 1);
- cout << '(' << z2.real() << ',' << z2.imag() << ')'
- << endl;
- cout << '(' << z2.rho() << ',' << z2.theta() << ')'
- << endl;
- z1 = z1 + z2;
- cout << '(' << z1.real() << ',' << z1.imag() << ')'
- << endl;
- cout << '(' << z1.rho() << ',' << z1.theta() << ')'
- << endl;
- return 0;
- }
- // End of File
-
-
- Listing 4 A rudimentary class for complex numbers using "lazy" evaluation and
- caching for polar form
- // z2.cpp
-
- #include <iostream.h>
- #include <iomanip.h>
- #include <math.h>
-
- class complex
- {
- public:
- complex(double r, double i);
- complex(const complex &z);
- complex &operator=(const complex &z);
- ~complex();
- double real() const;
- double imag() const;
- double rho() const;
- double theta() const;
- private:
- double re, im;
- struct polar;
- polar *p;
- };
-
- struct complex::polar
- {
- polar(double r, double t);
- double rho, theta;
- };
-
- inline complex::polar::polar(double r, double t)
- : rho(r), theta(t)
- {
- }
-
- inline complex::complex(double r, double i)
- : re(r), im(i), p(0)
- {
- }
-
- inline complex::complex(const complex &z)
-
- : re(z.re), im(z.im), p(0)
- {
- }
-
- inline complex &complex::operator=(const complex &z)
- {
- re = z.re;
- im = z.im;
- p = 0;
- return *this;
- }
-
- inline complex::~complex()
- {
- delete p;
- }
-
- inline double complex::real() const
- {
- return re;
- }
-
- inline double complex::imag() const
- {
- return im;
- }
-
- double complex::rho() const
- {
- if (p == 0)
- {
- complex *This = const_cast<complex *>(this);
- This->p =
- new polar(sqrt(re*re + im*im), atan2(im, re));
- }
- return p->rho;
- }
-
- double complex::theta() const
- {
- if (p == 0)
- (polar *&)p =
- new polar(sqrt(re*re + im*im), atan2(im, re));
- return p->theta;
- }
-
- complex operator+(const complex &z1, const complex &z2)
- {
- return complex
- (z1.real() + z2.real(), z1.imag() + z2.imag());
- }
-
- int main()
- {
- // same as Listing 3
- }
- // End of File
-
-
-
- Listing 5 A rudimentary class for complex numbers using a mutable member to
- implement "lazy" evaluation and caching for polar form
- // z3.cpp
-
- #include <iostream.h>
- #include <iomanip.h>
- #include <math.h>
-
- class complex
- {
- public:
- complex(double r, double i);
- complex(const complex &z);
- complex &operator=(const complex &z);
- ~complex();
- double real() const;
- double imag() const;
- double rho() const;
- double theta() const;
- private:
- double re, im;
- struct polar;
- mutable polar *p;
- };
-
- // ... same as Listing 4 ...
-
- double complex::rho() const
- {
- if (p == 0)
- p = new polar(sqrt(re*re + im*im), atan2(im, re));
- return p->rho;
- }
-
- double complex::theta() const
- {
- if (p == 0)
- p = new polar(sqrt(re*re + im*im), atan2(im, re));
- return p->theta;
- }
-
- complex operator+(const complex &z1, const complex &z2)
- {
- return complex
- (z1.real() + z2.real(), z1.imag() + z2.imag());
- }
-
- int main()
- {
- // same as Listing 3 and Listing 4
- }
- // End of File
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- On the Networks
-
-
- Relate Your Way Through the Storm
-
-
-
-
- Sydney S.Weinstein
-
-
- Sydney S. Weinstein, CDP, CCP is a consultant, columnist, lecturer, author,
- professor, and President of Myxa Corporation, an Open Systems Technology
- company specializing in helping companies move to and work with Open Systems.
- He can be contacted care of Myxa Corporation, 3837 Byron Road, Huntingdon
- Valley, PA 19006-2320, or via electronic mail using the lnternet/USENET
- mailbox syd@Myxa .com. (dsinc!syd for those that cannot do Internet
- addressing).
-
-
- One of the highlights the past few months has been typhoon from Thomas B.
- Pedersen <zeppelin@login.dknet.dk>. It's a complete, freely available
- relational database management system for the UNIX and OS/2 environments. He
- contributed it for posting in comp.sources.misc as Volume 44, Issues 57-65.
- typhoon was originally inspired by Raima's db_VISTA (today Raima Data Manager)
- but is relational rather than network based. typhoon lacks some of db_VISTA's
- features, but also contains a number of nice features not found in db_VISTA.
- All relations are defined in a Data Definition Language (dd1) file. You define
- the database relations like you would write a C structure with chars, ints,
- strings, multi-dimensional arrays, nested union, and structures, etc. Then you
- define primary, alternate, and foreign keys for each relation. The Data
- Definition Language Processor (ddlp) compiles the database definition into a
- binary file which constitutes the database description. The database relations
- are accessed via C subroutines which manipulate individual records within a
- table. typhoon supports the following features:
- multiple open databases
- multi-field keys
- nested structures in records
- controlled unions
- referential integrity
- variable-length fields
- null keys (optional keys in db_VISTA, but easier to use than db_VISTA's)
- dynamic opening and closing of database files
- At present the database has no locking mechanism but it's in the works.
- Currently the author is working on extending the library to support locking,
- logging, transactions, and paging (to improve performance).
- The library comes with three additional utilities:
- dbdview displays the database definitions
- tyexport exports tables from a database
- tyimport imports tables into a database
- Additional tools awaiting release, but being held up due to documentation
- issues, include online backup, restore, and a replication server.
- Continuing on with highlights from comp.sources.misc, I report that an
- extension to Perl 4 to allow access to Sybase databases was re-released at
- version 1.011 as sybperl in Volume 43, Issues 46-47 by Michael Peppler
- <mpeppler@itf.ch>. Features added since the last release (1.009) include
- dbfreebuf, dbsetopt, support to set the language and character set on the fly
- via DBSETLCHARSET and DBSETLNATLANG, access to non-ASCII text, extensions to
- eg/dbschema.pl to extract stored procedures and views as well as tables, casts
- of data types now performed with DBlibrary typedefs instead of C datatypes,
- and some bug fixes. Note: This package does not work with the new Perl 5
- version, just with the older Perl 4 versions.
- Brad Eacker <beacker@sgi.com> contributed a set of tools and library routines
- to manipulate xbase files for Volume 43, Issues 48-49. dbf includes tools to
- list all the records in the file, extract a specific record, add or mark as
- deleted a specific record and to removed marked deleted records from the file.
- It also includes a utility to create the template file and a basic grammar
- recognizer for xbase .prg files.
- A C++ class library to perform generic processing of gray-scale images was
- contributed for Volume 43, Issues 51-55 by Kiselyov Oleg
- <oleg@ponder.csci.unt.edu>. grayimage works on both images or rectangular
- areas within images, and can perform addition, scalar product, and
- modification of pixel values, based on equalization, histogram and
- filtration/convolution. The library implements morphological filtration as
- well. The package can read/write XWD, Group G TIFF, and PGM file formats
- selecting the appropriate method automatically.
- The latest version of zsh, an extended UNIX shell based on the Bourne shell
- syntax, was released for Volume 43, Issues 89-107 by Bas de Bakker
- <zsh-list@sterling.com>. zsh adds lots of features to the standard Bourne
- shell syntax, including command-line editing, complete with programmable
- command and file completion; multi-line commands; variable editing; command
- buffer stack; printing text into the editing buffer; menu completion and
- inline expansion of variables; and history commands. zsh also adds an enhanced
- globbing package for wildcard expansion, including recursive globbing (like
- the UNIX find command) and file attribute qualifiers. It supports an enhanced
- redirection syntax including tee-like features, named directories, internal
- integer arithmetic commands, manipulation of array variables, and even
- spelling correction. zsh runs on almost any UNIX system.
- The latest version of Project Info-ZIP's <zip-bugs@wkuvx1.wku.edu> portable
- UnZip utility was released as Volume 44, Issues 66-85. Version 5.12, is the
- first post of the 55.1 version and includes a self extraction stub, a rewrite
- of unshrink to avoid copyfight problems, -C option for case-insensitive
- filename matching, an added -L option to auto-convert filename case from upper
- to lower, wildcard support in UnZip itself, extraction to a specified
- directory other than the current directory, and performance tuning and bug
- fixes. While compatible with PKUNZIP, UnZip supports UNIX (many flavors), VMS,
- OS/2, MSDOS (+ Windows), NT, TOPS-20 (partly), AmigaDOS, Atari TOS, Macintosh,
- and Human68k. UnZip features not found in PKUNZIP include source code; default
- extraction of directory trees (with a switch to defeat this, rather than the
- reverse); VMS, Macintosh, and OS/2 extended file attributes; and, of course,
- the ability to run under most of your favorite operating systems.
- The latest version of the JPEG image compression software was contributed by
- the Independent JPEG Group <jpeg-info@uunet.uu.net> for Volume 44 Issues
- 98-124. This package contains C software to implement JPEG image compression
- and decompression. JPEG is a standardized compression method for full-color
- and gray-scale images. JPEG is intended for real-world scenes; cartoons and
- other non-realistic images are not its strong suit. JPEG is lossy, so the
- output image is not identical to the input image. The user can trade off
- output image quality against compressed file size by adjusting a compression
- parameter.
- Version 5 of the jpeg system includes a reusable JPEG
- compression/decompression library, plus sample applications cjpeg and djpeg,
- which perform conversion between JPEG JFIF format and image files in PPM/PGM
- (PBMPLUS), GIF, BMP, Utah RLE, and Targa formats. Two small applications,
- wrjpgcom and rdjpgcom, insert and extract textual comments in JFIF files. The
- package is highly portable; it has been used successfully on many machines
- ranging from Apple IIs to Crays. Version 5 includes user-level improvements
- over version 4:
- Automatic configuration, which simplifies installation for most UNIX systems
- A range of speed vs. image quality tradeoffs including image resizing during
- decompression (scaling down by a factor of 1/2, 1/4, or 1/8 is handled very
- efficiently)
- New programs rdjpgcom and wrjpgcom that allow insertion and extraction of text
- comments in a JPEG file
- BMP file format support within programs cjpeg/djpeg
- The application programmer's interface to the library has changed completely.
- Some of the most notable improvements for the programmer are as follows:
- The need for callback routines for handling the uncompressed image data has
- been eliminated. The application now sees the library as a set of routines
- that it calls to read or write image data on a scanline-by-scanline basis.
- The application image data is represented in a conventional interleaved-pixel
- format, rather than as a separate array for each color channel. This form of
- representation can save a copying step in many programs.
- The handling of compressed data has been cleaned up: the application can
- supply routines to source or sink the compressed data.
- All static state data has been eliminated from the library, so that multiple
- instances of compression or decompression can be active concurrently.
- JPEG-abbreviated datastream formats are supported.
- The documentation of the library has improved.
- Version 4.0 of dmake from Dennis Vadura <dvadura@plg.uwaterloo.ca> was
- released in Volume 45 Issues 1-27. dmake supports significant enhancements
- over other versions of Make, including:
- support for portable makefiles
- ability to run on many platforms (DOS, UNIX, Apollo, OS/2, Atari, MAC, and
- many others)
- significantly enhanced macro facilities
- sophisticated inference algorithm supporting transitive closure on the
- inference graph
- support for traversing the file system both during making of targets and
- during inference
- %-meta rules for specifying rules to be used for inferring prerequisites
- conditional macros
- proper support for libraries
-
- parallel making of targets on architectures that support it
- attributed targets
- text diversions
- group recipes
- swapping of itself to disk under MSDOS
- support for the MKS extended argument passing convention
- highly configurable features
- I am always asked for easy-to-use editors for use by novices with Electronic
- Mail packages. One that appeared this time was ee (EasyEdit) from Hugh F.
- Mahon <hugh@nsmdserv.cnd.hp.com>. Posted in Volume 45, Issues 29-33, EasyEdit
- is intended to be a simple, easy-to-use terminal-based screen-oriented editor
- that requires no instruction to use. It supports menus and a help window,
- eight-bit characters and localization, and simple paragraph formatting
- capabilities.
- yorick, a very fast interpreted language designed for scientific computing and
- numerical analysis, was contributed by David H. Munro <munro@icf.llnl.gov> for
- Volume 46, Issues 71-138. Its syntax is similar to C, but without declarative
- statements. yorick provides a very rich selection of multi-dimensional array
- indexing operations. It also features a binary I/O package that automatically
- translates floating-point and integer representations on the machine where it
- is running to and from the format of any other machine. Thus, you can easily
- share binary files between, for example, Cray YMPs and DEC Alphas, or "teach"
- yorick to read existing binary databases. yorick also offers an interactive
- graphics package based on X-Window. X-Y plots, quadrilateral meshes, filled
- meshes, cell arrays, and contours are supported. Finally, you can embed
- compiled routines in custom versions of yorick to solve problems for which the
- interpreter is too slow.
- With the rash of WWW servers on the net, checking the pages for proper syntax
- before publishing them is difficult. Several HTML authoring systems have been
- released, and now Henry Churchyard <churchh@uts.cc.utexas.edu> has contributed
- htmlcheck for Volume 47, Issues 48-54. Version 4.0. The program checks for
- quite a number of possible defects in the HTML (Hyper-Text Mark-up Language)
- version 2.0 SGML files used on the World-Wide Web. htmlcheck is easy to use,
- and gives lots of information including advice about many stylistically bad
- practices. It can do local cross-reference checking and generate rudimentary
- reference-dependency maps. htmlcheck will run on any platform for which an awk
- or perl language interpreter is available.
- This release of htmlchek also includes a number of supplemental utilities,
- including htmlsrpl.pl, the HTML-aware search-and-replace program. This utility
- uses either literal strings or regular expressions; acts either only outside
- HTML/SGML tags, or only within tags; can he restricted to operate only within
- and/or only outside specified elements; and can also uppercase tag names.
- Other utilities are as follows:
- makemenu Makes simple menu for HTML files, based on each
- file's <TITLE>; this utility can also make a simple
- table of contents based on <H1>-<H6> headings
- xtraclnk.pl Extracts links/anchors from HTML files; isolates
- text contained in <A> and <TITLE> elements
- dehtml Removes all HTML markup, preliminary to spell
- check
- entify Replaces high Latin-1 alphabetic characters with
- ampersand entities for safe 7-bit transport
- metachar Protects HTML/SGML meta-characters "&<>" in
- plain text that is to be included in an HTML file.
- htmlcheck is a must for checking out your web pages before releasing them to
- the world.
- Another HTML tool posted recently was m2h from Lawrence A. Coon
- <lac@cs.rit.edu>. Contributed for Volume 47, Issues 3-7, it's a conversion
- tool for translating documents written using troff/me macros into HTML. All
- standard me macros and many troff requests are handled, including paragraphs,
- sections, displays, indexes, delayed text, footnotes, and font specifications.
- m2h also provides support for user and predefined strings, and equations and
- tables, but not for user-defined macros. A local extension to me (.FI) allows
- inclusion of xfig figures that m2h converts to inline GIFs. The special HTML
- characters '<','>', and '&' are escaped and can be used in the me document
- without problems.
- If you want to be able to perform FIP transfers with a C program, then libftp
- from Oleg Orel <orel@oea.ihep.su> is what you need. Posted in Volume 47,
- Issues 29-36, libftp is a simple C interface to the FTP protocol. The library
- contains FtpConnect(host), FtpLogin(user, passw, acct), FtpGet(File),
- FtpDir(filespec), FtpDebug(handler), FtpFullOpen(remote or local, read or
- write), FtpError(handler) and many other functions. Also included is uftp
- (universal file transfer program), which is a user-interactive and
- non-interactive program to the ARPANET File Transfer Protocol (RFC959).The
- uftp client allows the user to transfer files, and groups of files in
- foreground and background modes.
- On the patch front, dist-3.0, the Perlbased configuration script generator and
- related toolset received several patches over the past few months. As usual
- these patches added modules to the configuration library, fixed some bugs, and
- extended the portability of other modules. It also added support for the use
- of Perl 5.0 to the dist toolset. The six patch sets covering patches #27-48
- appeared in the following: Volume 43 Issues 9-11, 42-43, 46-49, Volume 45
- Issues 41-48, 55 and Volume 47 Issues 20-23.
- mailagent, the Perl-based mail thrower, received several patches during this
- interval. Included are Patch 12-16: Volume 44, Issues 66-69, Patch 17-19:
- Volume 44 Issues 135-137, and Patch 19-22: Volume 45 Issues 49-53, Patch 23:
- Volume 45, Issue 56, Patch 24-26: Volume 47 Issues 9-11, Patch 27-29: Volume
- 47 Issues 55-57. The list of new features and bug fixes is too long to mention
- in the space I have available but the biggest are the switch to Dist 3.0 and
- Perl 5 support.
-
-
- Shell Menus Revisited
-
-
- A commonly recurring theme in UNIX utilities is a menuing system to make tasks
- easier for users. Mike Howard <how%milhow1@uunet.uu.net> contributed
- simple_menu-3.1 to comp.sources.unix for Volume 28, Issues 125-133 with
- patches in Issues 202-204. simple_menu makes it easier to create, maintain,
- modify, and extend menus of common operating system tasks for users. Typically
- users need to run fairly complex pipelines and/or shell scripts. Command-line
- execution is not a viable option, especially for clerical personnel and even
- more so for executive personnel. (This is especially true for infrequently
- executed tasks.)
- Writing menus in shell script is problematic for a variety of reasons. For
- example, writing the prompt-request-response-execute/error loop for the
- zillionth time is a drag. Modifying a shell script menu often breaks the code,
- and the shell menus become excessively large, so it's difficult to even read
- and understand the old code in order to make modifications.
- This program greatly alleviates such problems by introducing the following
- modifications/additions:
- It implements the prompt-request-response-execute loop in a relatively clean
- manner.
- It embeds the shell script that does the work in a higher level language. This
- keeps the individual shell scripts small, isolates individual tasks from each
- other, makes them easier to find, and allows each shell script to execute in
- its own isolated environment, so that modifying one script does not break
- another.
- Each settable shell variable has a user displayed prompt associated with it
- and may have a default value, so that users never have to deal with the shell
- or options in any cryptic way.
- It supports sub-menus.
- It supports a minimal set of menu cosmetics.
- One new concept in computer communications is messenger-based systems.
- Messengers are protocol-unspecific instruction sequences that replace the
- protocol-specific messages on which ordinary computer communication protocols
- are based. Christian Tschudin <tschudin@cui.unige.ch> has contributed m0 for
- Volume 28, Issues 51-62. m0 (M-Zero) is a new high-level prograrmming language
- and execution environment designed for the messenger-based provision of
- computer communication services. As a language, it's very much analogous to
- PostScript. This experimental package contains a complete m0 interpreter and a
- 30-page report on m0, providing a brief introduction to the concept of
- messenger-based computer communications, and containing the m0 language manual
- and format definition.
- If you need to perform syslog like functions, but you are not running TCP/IP,
- Arnold D. Robbins's <arnold@skeeve.atl.ga.us> simple-syslog is for you. It's a
- complete BSD-4.4 compatible syslog, but instead of using IP, it writes to
- stderr. This new update uses ANSI C and stdarg.h, and appears in Volume 28,
- Issue 63.
- The UNIX utility from is very limited. Johnson Michael Earls
- <darkfox@netcom.com> has contributed a new version of his frowmho utility for
- Volume 28, Issues 92-93. Instead of just listing who your mail is from,
- fromwho tells you the total number of messages received; how many are new; and
- for each person who sent you mail, how many messages they sent, how many of
- those are new, and the subjects of the messages. fromwho supports both
- sendmail and MMDF-style mailboxes.
- TCP/IP networks introduced a BOOTP server to allow diskless nodes to download
- their configuration and boot images. Over the years many extensions to the
- original BOOTP server have been written by various OS vendors. Gordon W. Ross
- <gwr@mc.com> has combined all of the extensions and bug fixes into a single
- server and contributed that server, bootp-2.4.0 for Volume 28, Issues 115-118
- with a patch in Issue 119. It combines BOOTP, a bootp gateway program, NetBSD,
- Columbia, Solaris, SVR4, and HP extensions along with full support for names
- as well as IP numbers.
- X has xlock to lock the screen from unauthorized use and to provide a screen
- saver. Now standard terminals can have slock-1.1 from Wes Bachman
- <wbachman@chop.isca.uiowa.edu>. Contributed for Volume 28, Issue 154, slock
- locks a user's terminal on a UNIX system, using the curses capabilities as an
- interface. Unlike the standard lock command, this package includes a number of
- enhancements, including incorrect password entry logging and a screen saver to
- enhance security when the user is away from his/her computer.
- The output of ls -lR is not very useful when searching for files. It only
- provides the file name on the detail lines with directory lines preceding each
- new directory. This format is hard to use with grep. Dennis O'Neill
- <denio@scubed.scubed.com> solves this problem with fullpath-1.1.0 in Volume
- 28, Issue 146. full path-1.1.0 converts the standard ls-lR output format
- /home/denio/fullpath-1.1.0:
- -rw-r--r-- 1 denio 5945 Jun 23 11:08 fullpath.c
- to
- 1992/06/23 5945 /home/denio/fullpath-1.1.0/fullpath.c
- allowing grep once again to find the files. This is especially useful with the
- ls -lR files retrieved from ftp sites.
-
-
- Out of Space
-
-
- I am out of space for this month, but I've caught up on a large part of the
- backlog. Next time I'll highlight what's new and catch up on the backlog in
- the X sources group.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Editor's Forum
- I'm still jet lagged from my latest visit to Japan as I write these words. My
- son, Geoffrey, and I spent a long weekend in Kyoto -- a trove of Japanese
- culture and history mercifully spared by the Great Hanshin Earthquake. We also
- spent several days in Tokyo while I conducted the business that nominally
- justified our whirlwind visit.
- Geoffrey is fifteen and a video-game enthusiast of long standing. I think he
- enjoyed the consumer electronic marvels on sale in Akihabara (a shopping
- district in Tokyo) at least as much as the ancient temples, shrines, and
- gardens. He brought home a suitcase half full of gadgets, and still more
- incentive to keep learning his Japanese.
- What I learned was equally interesting, at least for my world. The market for
- embedded systems is large and growing in Japan. An important segment of that
- market is video games. And an important trend in that market segment is away
- from programming in assembly language toward developing video games in C. Game
- systems are finally powerful enough, and complex enough, that the clear
- benefits of a higher-level language outweigh the equally clear costs.
- C++ is still on the horizon for these folks, who still begrudge lost bytes and
- microseconds in a very competitive marketplace. But a separate sector of the
- Japanese market is happy at the prospect of better support for Japanese text
- processing in C++. Two of the more ambitious proposals adopted into the draft
- C++ Standard this past year have Japanese authorship. Both go a long way
- toward putting Kanji text processing on an equal footing with ASCII text in
- the Standard C++ library. Folks who write large applications in C++ now face a
- happier prospect for manipulating text based on large character sets.
- I always enjoy my visits to Japan. On a personal basis, I like many aspects of
- the culture and its heritage. Professionally, I like the dynamism and
- competition the Japanese bring to the business of software development. This
- trip was particularly fun, however, because I got to see many things anew from
- the perspective of an active fifteen-year-old. Our technological innovations
- are becoming his cultural heritage.
- P.J. Plauger
- pjp@plauger.com
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- New Products
-
-
- Industry-Related News & Announcements
-
-
-
-
- HP Announces Standard Template Library Accepted by ANSI/ISO
-
-
- Hewlett-Packard Company has announced that its Standard Template Library (STL)
- has been accepted by the ANSI/ISO Standards Committee as a part of the
- international standard for C++. HP has placed its implemenatation of STL in
- the public domain. STL, designed by HP laboratories' researchers Alexander
- Stepanov and Meng Lee, provides a set of programming guidelines and a
- collection of generic software components.
- STL contains a broad set of algorithms that perform the most common kind of
- data manipulations programmers use, including searching, sorting, merging,
- copying, and transforming. The algorithms work with data types residing in
- different data structures. For example, the find algorithm in STL works on
- lists, vectors, files, or data structures for which the concept of finding
- makes sense.
- STL also formally defines iterator, the standard data-accessing concept. STL
- defines several categories of iterators, each providing a different way of
- accessing data. A forward iterator guides the algorithm through the data from
- beginning to end. A bi-directional iterator lets the algorithm move forward
- and backward. A random-access iterator can jump around. All the algorithms are
- written in terms of these abstract categories. Because of this, one
- implementation of an algorithm works with any type of data structure.
- The implementation of STL may be obtained by anonymous ftp from
- butler.hpl.hp.com in the directory/stl. For more information contact
- Hewlett-Packard Company, 3000 Hanover St., Palo Alto, CA 94304.
-
-
- Pure Software Upgrades Quantify
-
-
- Pure Software has upgraded Quantify, a tool which analyzes a program's
- run-time behavior. Quantify is based on Pure Software's Object Code Insertion
- (OCI) technology, which counts the number of instruction cycles. Because OCI
- is not dependent on source-code, OCI lets Quantify analyze the entire
- application, including shared and third-party libraries.
- Quantify 2.0 can automatically compare one Quantify run with another. In
- addition, Quantify 2.0 supports multiple-threaded applications including
- Solaris threads, DCE threads, and lightweight processes under SunOS. With
- Quantify 2.0, developers can identify performance details per thread. Other
- features of Quantify 2.0 include a "river of time" graph, point-and-click
- subtree analysis capability, and an interface that includes intuitive menu
- bars and helps.
- Quantify 2.0 supports SPARC workstations running SunOS or Solaris and HP9000
- workstations running HP-UX. Quantify 2.0 is priced at $1,198 per developer,
- with a minimum order of three. Annual upgrades and support are an additional
- $250 per simple license. Existing Quantify users on maintenance will be
- upgraded to Quantify 2.0 for free. For more information contact Pure Software
- Inc., 1309 S. Mary Ave., Sunnyvale, CA 94087; (408) 720-1600; FAX: (408)
- 720-9200; E-mail: info@pure.com.
-
-
- Neuron Data Introduces C/S ELEMENTS++
-
-
- Neuron Data has introduced C/S ELEMENTS++, a tool for building next-generation
- client/server applications. C/S ELEMENTS++ is based on C/S ELEMENTS 1.5, an
- application development environment for building enterprise client/server
- applications spanning multiple platforms and data sources. In addition to
- graphical interface development and script functions, C/S ELEMENTS provides
- transparent data-access capabilities. C/S ELEMENTS 1.5 includes a
- data-source-independent, extensible API that lets developers manage
- connections, queries, joins, and updates across multiple data sources. C/S
- ELEMENTS provides read-write native access to Oracle, Sybase, Informix, and
- Ingres databases, as well as access to ODBC-compliant databases such as DB2,
- HP ALLbase/SQL, and SQL Server. Additional features of C/S ELEMENTS 1.5
- include: DBView, a flexible, point-and-click data viewing facility that
- separates the logical view from the physical data source; and Data Link
- Editor, which lets developers define relations between GUI objects and the
- DBView using point-and-click visual programming. C/S ELEMENTS 1.5 also
- includes the following object classes: Connection Object, Query Object, and
- Virtual Table Object. Other features of C/S ELEMENTS 1.5 include support for
- both asynchronous queries and standard database types.
- C/S ELEMENTS++ combines the capabilities in C/S ELEMENTS with C++ support.
- Features of C/S ELEMENTS++ include a separate C++ API that incorporates its
- own C++ libraries, as well as third-party libraries. In addition, C/S
- ELEMENTS++ automatically generates both C++ application code and class
- definitions. Additional C/S ELEMENTS++ features include the following: virtual
- functions that support advanced, object-oriented programming techniques such
- as customization by subclassing, representation of both GUI and non-GUI
- objects as C++ classes, and support for C++ in the Data Access Element via the
- C++ API.
- C/S ELEMENTS++ supports Microsoft Windows, Sun Solaris, SunOS, and
- HP9000/HP-UX systems. C/S ELEMENTS++ is priced at $6,850 for a developer and
- deployment license. There are no run-time fees. For more information contact
- Neuron Data, Palo Alto, CA; (415) 321-4488.
-
-
- ASTA Upgrades QA C
-
-
- ASTA, Incorporated has upgraded QA C, an automated system which analyzes C
- source code, automates software development code reviews, and automates
- conformance to programming standards. QA C is a configurable analysis system
- used to analyze C source code, check for 800 different types of problems, and
- enforce company-specific programming standards. QA C automates code reviews
- and identifies portability, maintainability, reliability, and stylistic
- issues. By providing an automated mechanism for documenting and controlling
- the quality of source code, QA C can serve as the foundation for Software
- Process Maturity and ISO9000 quality initiatives.
- QA C v4.0 includes support for analyzing C code that contains embedded SQL
- statements. With QA C v4.0, users will be able to parse C code with embedded
- SQL statements prefixed by EXEC SQL or $ and identify syntax errors,
- non-portable SQL, and invalid or questionable coding practices. QA C can
- access relational databases including those from Sybase, Ingres, Oracle, and
- Informix.
- QA C supports UNIX platforms including those from SUN, HP, DEC, and IBM. QA C
- is also available on 386/486/Pentium PCs running SCO UNIX. A QA C v4.0 license
- starts at $6,000. For more information contact ASTA Incorporated, 1 Chestnut
- St., Suites 205/206, Nashua, NH 03060; (800) 350-2782 or (603) 889-2230; FAX:
- (603) 881-3740.
-
-
- Excel Upgrades CASE Tool Suites
-
-
- Excel Software has upgraded its MacAnalyst and MacDesigner suite of CASE tools
- to generate C/C++, Pascal, Object Pascal, Basic, FORTRAN, or SQL source code
- from software design diagrams. Development teams using object-oriented design,
- structure design, or data modeling can generate header files and function code
- frames for their software. The generated code is target-independent.
- With MacAnalyst and MacDesigner v4.3, object-oriented designers can draw
- diagrams using Booch, OMT, Coad/Yourdon, or Shlaer/Mellor notations with
- dictionary-defined class attributes and operations. Information for attributes
- and operations include data types and function argument lists. Source code
- consisting of C++ or Object Pascal interface files and function code frames
- can be generated.
- Features of the tool suite for database designers include entity-relation
- diagrams (ERDs), customizable Details dialog, Data Dictionary, and foreign
- keys. Also ANSI-standard SQL can be generated to create the database for a
- relational database system (RDBMs). Designers using structured analysis and
- design methods can add function-return data types and function argument lists
- to each model and automatically generate function code frames in either C,
- Pascal, Basic, or FORTRAN.
- Other features of the MacAnalyst and MacDesigner Tool Suite v4.3 include
- customization options and a project menu. Code can be generated for a set of
- design diagrams or added incrementally to existing code files for selected
- diagram objects.
- Prices for the MacAnalyst and MacDesigner CASE tool suite v4.3 range from $995
- to $2,995 per copy. Site licensing is also available. For more information
- contact Excel Software, P.O. Box 1414, Marshalltown, IA 50158; (515) 752-5359;
- FAX: (515) 752-2435; E-mail: casetools@aol.com.
-
-
- Cimulus Releases UPData
-
-
- Cimulus Automation Systems, Inc. has released UPData, a user-profiling tool.
- UPData includes both a Microsoft Windows DLL and DOS C library. User profiling
- allows developers to observe how users interact with their products. UPData
- monitors a user's actions, such as which option they select, in what order,
- and how often. Information about which commands are most or least commonly
- used or in which operation or dialogs they spend most of their time can be
- collected and logged without interfering with the user or the product.
- Additionally, UPData can track internal warnings and error conditions, and
- lets users add their own suggestions to the log.
- UPData stores the information in a compressed data file, which can be returned
- by a user to the developer for analysis. Based on that data, developers can
- focus their efforts on the areas where they can provide the most benefit. For
- example, defaults and options can be set to streamline the interface and make
- it smoother and more intuitive to use; coded optimization can be made to
- frequently-used routines; and areas where documentation, training, or online
- hints would help most can be identified. When UPData is used to track internal
- code warnings and errors, a problem discovered by a user will automatically
- generate an accurate log of the condition that led up to the error.
- UPData is priced at $79.95 and contains both the Windows and DOS versions. For
- more information contact Cimulus Automation Systems, Inc., 2901 Hubbard Rd.,
- Ann Arbor, MI 48105; (313) 769-4108.
-
-
-
- Abraxas Releases CodeCheck 5.0
-
-
- Abraxas Software, Inc. has released CodeCheck 5.0, an analysis tool that
- automates the C++ Quality Assurance process. CodeCheck 5.0 validates software
- standards automatically using Abraxas' Expert System Technology. Quality
- assurance personnel can "teach" CodeCheck 5.0 their company standards and
- CodeCheck 5.0 will validate source code that is in compliance. Using CodeCheck
- 5.0, individual programmers can "correct" their code before submission to the
- Quality Assurance Manager.
- According to Abraxas, CodeCheck 5.0 reads all variants of C/C++ source code.
- CodeCheck 5.0 supports Windows 95, Macintosh, OS/2, and Windows NT operating
- systems. CodeCheck 5.0 also supports UNIX network environments. Prices for
- CodeCheck 5.0 range from $495 to $1,995 per seat. Source code is available for
- license on minicomputer and mainframe sites. For more information contact
- Abraxas Software, Inc., 5530 S.W. Kelly Ave., Portland, OR 97201; (503)
- 244-5253; FAX: (503) 244-8375; E-mail: abraxas@ortel.org.
-
-
- MetaWare Offers Compiler Controls
-
-
- MetaWare has begun offering C/C++ native and cross compilers with a software
- work-around for the Pentium floating-point division error. MetaWare High C/C++
- for extended DOS, Windows, and OS/2 on Intel platforms offers compiler
- controls that check for the existence of the Pentium floating-point processor
- and generate Intel-compliant versions of Pentium-safe floating-point divide
- code when needed. Quoting Dr. Thomas Pennello, MetaWare's co-founder and vice
- president, "The generated code is safe for all representation of single,
- double, and extended-precision numbers, and the core routines used in the fix
- are fully compliant without the Intel-approved software work-around."
- Additionally, High C/C++ reports the number of times a bad division result
- could occur without Pentium-safe floating-point division code. Developers can
- also analyze increases to execution time that result when using a software
- solution. According to David Wilcox, manager of technical support at MetaWare,
- "The software solution will be made available to customers with current
- versions of High C/C++ for extended DOS, Windows, and OS/2. New versions of
- High C/C++ for extended DOS, Windows, and OS/2 will include the Pentium fix."
- For more information contact MetaWare Incorporated, 2161 Delaware Ave., Santa
- Cruz, CA 95060-5706, (408) 429-6382; FAX: (408) 429-9273; E-mail:
- techsales@metaware.com.
-
-
- Visix Ships Galaxy Visual Builder Integration Kit
-
-
- Visix Software Inc. has begun shipping the Galaxy Visual Builder Integration
- (VBI) Kit, a product that lets developers customize and extend the Galaxy
- Application Environment. Galaxy is a cross-devlopment platform designed for
- creating scalable applications that are graphical and distributed.
- With the VBI Kit, developers can create and integrate specialized visual
- editors into the Galaxy Visual Resource Builder, letting the developer work
- with custom and specialpurpose objects without leaving the Galaxy visual
- environment. The VBI Kit also lets developers modify standard Galaxy editors
- to operate on extended Galaxy objects within the Visual Resource Builders or
- replace galaxy visual editors with editors tailored to corporate objects, or
- modify the function of standard Galaxy visual editors to ensure that objects
- are used consistently across development groups.
- Galaxy supports UNIX, Macintosh, Microsoft Windows, Windows NT, OS/2, and
- OpenVMS platforms. The Visual Builder Integration Kit is priced at $4,995. For
- more information contact Visix Software Inc., 11440 Commerce Park Dr., Reston,
- VA 22091; (800) 832-8668 or (703) 758-8230; FAX: (703) 758-0233.
-
-
- LOOX Introduces LOOX 3.0
-
-
- LOOX Software, Inc. has introduced LOOX 3.0, an object-oriented graphics
- development tool for X-Windows. LOOX lets developers create interactive
- graphics for UNIX applications using LOOXMaker, a vector-based graphical
- editor, and LOOXLib, a vector-based, object-oriented C function library.
- Features of LOOX 3.0 include a communications protocol and 15 types of
- two-dimensional charts. In addition, LOOXMaker generates code and includes a
- drawing browser. LOOXLib contains a class of ready-to-use dynamic objects and
- two classes of vector objects, the PARALLELOGRAM class and the SUBDRAWING
- class. LOOXLib also includes methods and resources for creating customizable
- vector objects. The methods include detecting a user-click with the right
- mouse button and designing editable text objects. LOOX 3.0 also includes
- on-line documentation, a tutorial, and source code for various sample
- programs.
- LOOX 3.0 supports SUN OS 4.1.x, Solaris 2.x, HP-UX 9.0, AIX 3.x, SGI IRIX,
- Solaris x86, UNIXWare, SCO, and X Terminals with X11R4 or X11R5. LOOX 3.0
- requires Motif, X11R4, or X11R5. LOOX 3.0 is priced at $9,950 per license.
- There are no run-time fees. For more information contact LOOX Software, Inc.,
- 4962 El Camino Real, Suite 206, Los Altos, CA 94022; (415) 903-0942; FAX:
- (415) 903-9824.
-
-
- Kofax Releases KIPP ImageControls
-
-
- Kofax Image Products has released KIPP ImageControls v1.0, a suite of Visual
- Basic tools for imaging applications. The suite of VBX-based tools lets users
- develop new systems or image-enable legacy applications with high-volume
- document image processing capabilities. KIPP ImageControls is compatible with
- Microsoft Visual Basic and Visual C++.
- Available in two editions, the Standard Edition of KIPP ImageControls provides
- image scan, print, and display capabilities. The Gold Edition includes
- sophisticated image processing features required in high-volume applications.
- The Gold Edition includes the following image processing features: bar code
- recognition during scanning; image deskewing to align misfed documents
- electronically to improve optical character recognition accuracy; job
- separation detection to identify the start of a new document in batches; and
- text annotation to electronically mark scanning date, time, or other
- information on the image. Both editions support rated-speed scanner operations
- for 100 scanners operating from 10 ppm to 100 ppm, and network printing at up
- to 17 ppm.
- The Standard Edition of KIPP Image Controls is priced at $995. The Gold
- Edition is priced at $2,995. There are no royalty fees or licensing
- restrictions. For more information contact Kofax Image Products, 3 Jenner St.,
- Irvine, CA 92718-3807; (714) 727-1733; FAX: (714) 727-3144.
-
-
- Blue Sky Upgrades RoboHELP
-
-
- Blue Sky has upgraded RoboHELP, a help-authoring tool for Windows and Windows
- NT. RoboHELP 3.0 is bundled with WinHelp Video Kit and includes the tools
- needed for integrating and playing video and sound into Help systems. Features
- of RoboHELP 3.0 include support for special Word 6.0 features and support for
- European versions of Work 6.0. Other features of RoboHELP 3.0 include four
- HelpCheck tools, a macro hotspot editor, and documentation templates.
- Features of the WinHelp Video Kit include Software Video Camera, a full-motion
- screen action recording program; Video Wizard for adding video and sound to
- Help projects; a video tester for previewing video and sound files; WinHelp
- Video/Sound Player for video and sound playback in Help files; and Video for
- Windows 1.1d Runtime.
- The RoboHELP 3.0 and WinHelp Video Kit bundle is priced at $499. The WinHelp
- Video Kit can be purchased separately for $99. WinHelp Video Kit includes a
- royalty-free license to distribute the video/sound player DLL. Registered
- users of RoboHELP 2.0 and 2.6 can upgrade to the RoboHELP 3.0 and WinHelp
- Video Kit for $129. For more information contact Blue Sky Software Corp., 7486
- La Jolla Blvd., Suite 3, La Jolla, CA 92037; (800) 677-4946 or (619) 459-6365;
- FAX: (619) 459-6366.
-
-
- Microway Announces Fix for FDIV Bug
-
-
- Microway has announced a software fix for the Intel FDIV bug. For execution
- speed reasons, Microway NDP compilers generate inline x87 code. According to
- Microway, the inline fix for FDIV executes in less that 30 cycles, does not
- substantially increase the execution times of floating-point intensive
- applications, and can be adjusted to guarantee the precision of single-real,
- double-real, and temporary-real operations. This fix only works for
- applications which have been recompiled with Microway compilers.
- The software fix will be available free of charge to owners of 386, 486, and
- Pentium optimized versions of NDP C/C++, NDP FORTRAN, and NDP Pascal. For more
- information contact Microway, Research Park, Box 79, Kingston, MA 02364; (508)
- 746-7341l FAX: (508) 746-4678; E-mail: stevef@microway.com.
-
-
- HyperAct Announces Three Products
-
-
-
- HyperAct has announced three products, Interactive Help for Windows (IH), an
- upgraded version of PASTERP, and XSpawn. IH is a WinHelp DLL extension
- language, which adds interactive forms and dialogs to WinHelp titles and links
- them dynamically to external resources. IH monitors and controls WinHelp
- activities, executes the standard WinHelp macros, and calls the WinHelp
- "Callback" internal functions API.
- PASTERP is a Pascal-Like interpreter with an embedded interface to C++ and
- Borland Pascal programs. The PASTERP development kit includes: Object, the
- PASTERP parse and execution engine implemented as a hierarchy of Borland
- Pascal Object classes; DLL, the PASTERP parser and engine implemented in a DLL
- that can be interfaced using the DLL's API; object-oriented frameworks; and
- support for dynamic functions.
- The XSpawn library contains a set of functions, which detect spawned program
- termination and its exit code, support synchronized spawning under WIN-OS/2
- and Windows NT, and detect termination and exit code of OS/2 and Win 32
- applications. XSpawn is shipped with header files and example programs for
- C/C++, Borland Pascal, and Visual Basic.
- IH is priced at $149. PASTERP is priced at $350 with royalty fees. XSpawn is
- priced at $99 with full basic source code included. For more information
- contact HyperAct, Inc., P.O. Box 5517, Coralville, IA 52241; (319) 351-8413;
- FAX: (319) 351-8413; CompuServe: 76350,333; Internet: rloewy@panix.com.
-
-
- Rational Integrates Rational Rose
-
-
- Rational Software Corporation has integrated its Rational Rose family of
- analysis and design tools with its SoDA documentation tool. Developers can
- automatically generate software documentation from their analysis and design
- models, and from their code. The Rational Rose family is a suite of graphical,
- object-oriented tools for analysis, design, and implementation of software
- applications. The tools support an iterative development cycle and includes
- capabilities for generating and reverse engineering C++, Ada, SQL Windows, and
- ObjectPro code.
- SoDA is available as an option to Rational Rose and supports workstations
- running SunOS and Solaris. Rational also plans an AIX version. SoDA is priced
- at $6,000. For more information contact Rational Software Corporation, 2800
- San Tomas Expressway, Santa Clara, CA 95051-0951; (800) 728-1212 or (408)
- 496-3600; FAX: (408) 496-3636; E-mail: product_info@rational.com.
-
-
- Xcc Releases Revision Control Engine
-
-
- Xcc Software has released the Revision Control Engine (RCE), a multi-platform
- programming library, which includes an API for revision and version control.
- RCE is based on Walter F. Tichy's RCS tool. RCE's API provides calls for
- version and version information, project management, interface dialogue, and
- error messages. The user interface can either be command or graphical
- (Microsoft Windows or OSF/Motif). Features of RCE include seamless integration
- with other application-development products and a delta algorithm. RCE stores
- deltas instead of full versions of data formats such as binary files, images,
- and sound.
- RCE supports Windows, Windows 95, Windows NT, OS/2, and UNIX. RCE is priced at
- $1,990 for Windows , or $3,330 for UNIX. For more information contact ESC,
- P.O. Box 1982, Lawrence, KS, 66044; (913) 832-2070;FAX: (913) 832-8787; or Xcc
- Software, Durlacher Allee 53, D-76131 Karlsruhe, Germany; +49 721-616474; FAX:
- +49 721-621384; E-mail: rce@xcc-ka.de.
-
-
- BSO/TASKING Announces Starter Pack And Power Pack
-
-
- Boston Systems Office/Tasking has announced the Starter Pack and Power Pack.
- The Starter Pack is an ANSI-C-based tool kit which includes a binary-mode
- compiler and a simulator debugger for direct migration of 8051 applications to
- the MCS 251 architecture. The Power Pack, a source-mode compiler, produces
- code for the 251 architecture. The Power Pack includes an ANSI C source-mode
- compiler with 251-style extensions, which support the instruction set and 16Mb
- address space of the MCS 251 microcontroller. Both the Starter Pack and Power
- Pack include the CrossView Windows 251 Simulation Debugger for C and assembly
- debugging.
- The Starter Pack supports PCs. The Power Pack supports PCs, Sun SPARC, Sun
- Solaris, and HP9000 HP-UX. Pricing for the Starter Pack starts at $395. For
- more information contact Boston Systems Office/Tasking, 333 Elm St., Dedham,
- MA 02026; (617) 320-9400; FAX: (617) 320-9212.
-
-
- CRC Press Introduces Numerical Library
-
-
- CRC Press, Inc. has introduced a numerical library in C for engineers and
- scientists. The library lets users solve numerical problems in areas of linear
- algebra, ordinary and partial differential equations, optimization, parameter
- estimation, and special functions of mathematical physics. Features of the
- numerical library include ANSI C for portability, a diskette with source code,
- a compact modular structure for implementation on a PC, and 130 pages of
- worked examples.
- For more information contact CRC Press, Inc., 2000 Corporate Blvd. N.W., Boca
- Raton, FL 33431; (800) 272-7737 ext. 2232 or (407) 994-0555; FAX: (800)
- 374-3401.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- We Have Mail
- Dear Editor:
- Please send me your Author Guidelines for C/C++ User's Journal. I have several
- ideas; however some are sure to melt away once I find out how much work is
- involved.
- Also, on page 95 of the October 94 issue, it says "Some programmers may wish
- to reuse the file_pointer member as a validity flag, instead of using a
- separate member. Although reusing file_pointer may save a byte or two per
- object, I feel that having a separate state member keeps the code more
- readable and maintainable."
- Okay, but you missed an important opportunity to demonstrate how C++ can give
- you the best of both worlds. Go ahead and internally use the file_pointer
- member as the state variable, but use the public interface is_valid() to hide
- this. If, in the future, other reasons for invalid objects come up, it's easy
- to add an explicit state member because the is_valid() calls are already used.
- To me, design trade-offs like this are exactly why C++ is more
- powerful/maintainable than C.
- Thanks and keep up the good work,
- Andy Krassowski
- andyk@sclara.qms.com
- You make a good point about class interface style. -- pjp
- Dear Mr. Plauger,
- I have been reading CUJ for the last two years and I enjoy it and find it very
- useful. It will be very good if all the articles published under "Code
- Capsules" or "Standard C" and "Stepping Up to C++" are made available at one
- place. Especially "Code Capsules" is very interesting. It will be useful to
- have all the articles under "Code Capsules" in a book form or as a special
- issue of CUJ. Do you intend to publish some articles on object-oriented
- programming?
- Yours faithfully,
- J.P. Singh
- Melbourne, VIC 3071
- AUSTRALIA
- Many, if not all, of my "Standard C/C++" columns are also to be found in two
- of my published books, The Standard C Library, and The Draft Standard C++
- Library, both published by Prentice Hall. I have been nagging both Dan Saks
- and Chuck Allison for years to produce books based on their respective columns
- as well. Talented as they are, they are both naturally too busy to find much
- spare time for such pursuits. I keep hoping, nonetheless. R&D Publications
- also continues to explore ways to make the material in their magazines
- available in other useful forms. So you should keep hoping too.
- We'll probably never do an article on object-oriented programming per se. At
- least, it won't be one of those preachy things telling you how to program
- properly, at last. We prefer to run more useful "how to" articles, written by
- practicing programmers, that cover such topics in passing. I persist in the
- belief that pragmatic examples of working code are a better teaching tool. --
- pjp
- Hi,
- I'm curious about a statement in Kenneth Pugh's article "Using C Libraries in
- C++" in the May 1994 issue of CUJ. There is a statement there to the effect
- that in C++ it is the responsibility of the called funtion to clean up the
- stack when returning.
- I'm probably missing something, but I've been sitting here for a while now
- trying to figure out how it's possible to have variable argument lists if the
- called function pops the stack on return. Assuming that the called function is
- called from more than one calling function, how would the compiler know how
- many and what type of arguments to remove from the stack when generating the
- code for the called function? Does it generate a copy of the function
- compatible with every invocation?
- Sorry if I'm missing something obvious.
- Joe Halpin
- jhalpin@netcom.com
- Ken should probably have qualified his statement a bit. It was a longstanding
- practice with C compilers that the caller removed arguments from the stack.
- Hence, it didn't hurt if the caller supplied more arguments than the callee
- expected. The C Standard introduced function prototypes so that, in the
- presence of a prototype at least, the callee could know how to clean the
- stack. The C Standard also requires that a function with a fixed number of
- arguments be called with the proper number. Compilers can thus now tailor
- their calling sequences accordingly. But C still requires the caller to clean
- the stack in one contex -- where the prototype declares that the function
- expects a varying number of arguments.
- C++ is even more restrictive. Every function call must occur in the scope of a
- prototype. Hence, except for the case of a function explicitly declared to
- accepty a varying number of arguments, the callee can reliably clean the
- stack. I hope that added bit of precision helps clarify your understanding. --
- pjp
- Hello Dr. Plauger:
- As a new programmer I find your articles interesting and helpful. I do
- experience some difficulties which I continually learn from due to compiler
- differences.
- In particular, the article "Extending the Windows File Manager" (Sept. 1994,
- pp. 37) by Trevor Harmon needed a bit of modification to compile and function
- properly using Borland V4.0. The compiler didn't like the EXPORTS section in
- the FMEXT.DEF file. All that is needed is to delete the EXPORTS section
- entirely and to add the _export keyword to the LibMain, WEP, ShowSelDlgProc,
- DriveInfoDlgProc, and FMExtensionProc functions in FMNEXT.C. Also, #pragma
- argsused can be used where apropriate to eliminate those pesky "variable not
- used" warnings.
- The new functionality is very interesting and I thank you and Mr. Harmon for
- providing it.
- Michael L. Rohwedder
- (ML-Rohwedder@bgu.edu)
- Thanks for the information. And welcome to the wonderful world of nonportable
- C extensions. -- pjp
- Dear editor,
- I read the article "A Simple Soundex Program" in the September 94 issue and
- discussed it with an associate here. I mentioned the May 1991 article
- referenced in this article about Levenstein Distances. This was titled
- "Inexact Alphanumeric Comparisons" by Hans Zwakenberg.
- I had read that article in 1991 and modified the algorithm some time ago to
- what I felt was a more efficient one. My version uses a single array and few
- other vairables as a sliding window instead of the double subscripted array in
- the origional code. My associate suggested that I send my example back to you,
- so here it is. [It's available on the monthly code disk. -- pjp]
- It consists of a PKZIP file (v2.04g) containing a source module for the LDIST
- code, a header file and two sample programs I wrote to test the functions. I
- wrote an additional list search function using the algorithm to return the top
- N matches (lowest L distances) from a list.
- I work for MCI in the Data Services Division and searching address lists for
- names is an important part of what we do here. Because of this, these
- algorithms, both the L Distance and the Soundex, interest me. Thanks.
- R. Bruce Roberts
- System Development Engineer
- MCI Data Services Division
- Thank you. -- pjp
- Dear Editor:
- Regarding the Sept '94 letter from Lee Shackelford in which he asked of OOP,
- "Why bother?" Lee raises a legitimate question that I don't feel is asked
- often enough in our zeal to try out exciting new technologies.
- Don't get me wrong, I'm not anti-OOP. I see benefits to the technology and
- have applied OOP to my work. But sometimes we adopt complex, new technologies
- in one big gulp without proper planning and proper consideration of the
- effects on our development processes.
- Haphazard introduction of new technologies can lead to disasters, as happened
- to Borland International. Borland committed its projects to use C++, causing
- huge delays in getting their products to market. These delays put Borland into
- deep financial trouble. [1] Perhaps Borland should have tried C++ on a pilot
- project first, then slowly introduced the technology to other projects.
- The moral of this story isn't to stop trying new technologies, just be careful
- doing so. And don't forget to ask "Why bother?" again and again.
- Mason Deaver
- AT&T Network Control Center - West
- Denver, CO 80205
- mcdeaver@attmail.com
- [1] Andrew Binstock, "Objectively Speaking," Viewpoint column, Unix Review,
- June 1994, pg. 7
- Amen. -- pjp
- Dear Mr. Plauger,
- Thank you very much for your illuminating review on the book "Software
- Internationalization and Localization" which appeared in the C Users Journal!
- Our organization, VTT, participates in a European research project, called
- GLOSSASOFT. VTT is a Finnish acronym for Technical Reseach Center of Finland.
- It is a non-profit research organization with 2,600 employees. The partners in
- the GLOSSASOFT project, in addition to VTT, are Open University from U.K.,
- NCSR Demokritos from Greece, Claris from Ireland, HP Hellas from Greece, and
- Bull from France. The aim of the project is to develop methods and guidelines
- for software internationalization and localization.
- VTT's expertise is centered around software development and therefore our
- concern is very much in the same area which you outline in your review. It
- seems that many of the current books and articles on internationalization and
- localization are written by specialists of localization. Good knowledge of
- programming is needed, of course, when internationalization is concerned.
- In addition to our thanks, I would like to ask your kind advice on the
- following subjects:
-
- what you consider the best sources of information related to the support in C
- for internationalization especially concerning future directions, and
- is there something that you could point out as a useful research effort in
- internationalization issues related to C, taken into account the work that is
- already being done e.g. in ISO?
- Best regards,
- Timo Honkela
- vtt.fi!Timo.Honkela
- I have written several times in these pages about internationalization. See
- the March through May 1991 and the May through July 1993 issues. Much of that
- material is also available in my book, The Standard C Library. Also, The
- Journal of C Language Translation, edited by John Levine (johnl@iecc.
- cambridge.ma.us) frequently prints articles on this topic. And Rex Jaeschke,
- Chair of X3J11, has an article you may find interesting in the pipeline for
- publication in this magazine.
- Ongoing standardization in the area of internationalization is unfortunately
- divided among several groups. Within ISO committee JTC1/SC22 are the working
- groups WG14 (C), WG15 (POSIX), WG20 (internationalization), and WG21 (C++).
- Then there's X/Open and CEN, in Europe. I have trouble keeping up myself. Good
- luck. -- pjp
- Dear Mr. Plauger,
- I have a couple of new tricks that I have recently learned. Having a
- programmer's instinctive desire to see people write code the way I would, I
- thought I might share them.
- The first involves numeric conversion constants. Most of us have encountered
- the everpresent definitions:
- #define DEGREES_ TO_RADIANS M_PI/180.
- #define RADIANS_TO_DEGREES 180./M_PI
- I recently had reason to also deal with mils (6,400 to a circle), bams
- (0x10000 to a circle) and sectors (we break the circle into 256 sectors for
- processing). (For the curious, this was part of a radar simulation.) Rather
- than defining 20 different conversion constants, I merely defined 180 degrees
- in each of the five units as follows:
- // 1/2 circle in various units
- // Usage example:
- // radval = degval * (RADIANS/DEGREES)
- #define BAMS ((float)0x8000)
- #define MILS 3200.0
- #define DEGREES 180.0
- #define SECTORS 128.0
- #define RADIANS M_PI
- I could then easily and naturally perform any conversion by combining the
- necessary constants, much as I learned in college physics:
- sectval = milval * (SECTORS/MILS);
- radval = sectval * (RADIANS/SECTORS);
- and so forth. This is extremely flexible, and suffers no loss of readability.
- The parentheses insure that a compiler with decent optimization will
- pre-compute the division at compile time, producing a single multiply. Without
- the parentheses, the expression may generate a multiply and a divide. I tested
- this with Borland C++ 4.0, and the parentheses do save a run-time divide
- there.
- The second trick is a way around a difficult, but fairly common, coding
- problem. I'm sure we have all run into loops like the following:
- fgets(string, MAXLEN, stdin);
- while (string[0] == '\n')
- {
- puts("Entering a blank line won't cut
- it.\n");
- fgets(string, MAXLEN, stdin);
- }
- The problem, of course, is the repetition of the fgets call. This duplication
- is undesirable. It's too easy to change one of the calls and miss the other
- (especially in more realistic examples).
- One solution is to change this to the following:
- for (fgets(string, MAXLEN, stdin);
- string[0] == '\n';
- fgets(string, MAXLEN, stdin))
- puts("Entering a blank line won't cut
- it.\n");
- This really doesn't cure the problem, and it abuses the iterative intent of
- the for loop.
- My favorite solution to this problem is:
- while (fgets(string, MAXLEN, stdin),
- string[0] == '\n')
- puts("Entering a blank line won't cut
- it.\n");
- This is an unconventional solution, but seems to me a very effective use of
- the comma operator. For cases like this, it may be one of the best solutions
- for a sticky problem. It is certainly better than:
- while (fgets(string, MAXLEN, stdin)[0] == '\n')
- puts("Entering a blank line won't cut
- it.\n");
- which will also work, but may be less welcome in a professional environment.
- Thanks for your time, and apologies to all the people who thought of these
- ideas before I did.
- Brian Tagg
- I like your use of manifest constants to handle multiple units. When it comes
- to fgets, however, I'm a bit more paranoid. I always check to see if it
- returns a null pointer. And I worry about whether it truncates a long line.
- Otherwise, I approve of your concern with developing a good and consistent
- programming style. -- pjp
- Dear Editor,
- This is a response to Edward Bell's letter (CUJ, March 1995), in which he asks
- for a program to generate random English pronounceable proper names.
- I assume that you want to randomly generate the characters that make up the
- names and not just pick names randomly from a list.
- I've tried this a few times. The only solution I like so far is to use a list
- of sample names to tabulate some statistics before generating the random
- names. I use about a 3- or 4-dimensional array to store the occurrences of the
- current character based on the previous characters.
- /* Something like ... */
- c0 = getchar();
-
- a[c2][c1][c0]++;
- c2 = c1;
- c1 = c0;
- You probably want to set the previous characters to spaces between each name
- to eliminate correlation between names.
- I'm usually willing to handle punctuation and case on my own, so I just make
- each dimension 27 elements, one for each letter of the alphabet, plus one more
- for spaces and everything else. Most of the elements will have a frequency of
- zero, so you should save memory if a sparse array is used. Three dimensions
- should give fairly pronounceable names; four dimensions will probably start
- duplicating real names regularly.
- To generate the names, total the occurrences for each posibility for the
- current character based on the previous characters and then generate a random
- number less than the total, and then find the character.
- /* Something like ... */
- c2 = c1;
- c1 = c0;
- n = 0;
- for (i = 0; i < dim_siz; i++) n +=
- a[c2][c1][i];
- r = rand() % n;
- for (i = 0; i < dim_siz; i++) {
- if (r < a[c2][c1][i]) {
- c0 = i; /* "c0" is the next char. */
- break;
- }
- r -= a[c2][c1][i];
- }
- putchar(c0);
- A potential problem is that the random names tend to vary more in length than
- real names. If this is a problem, you can vary the weight of the space
- character based on the current name length.
- References: (Old) Cryptography books often have information about
- frequently-occurring character sequences. Data compression algorithms based on
- frequently-occurring character sequences might be a good source for efficient
- storage methods.
- Sheldon
- Thanks -- pjp
- Dear Mr. Plauger,
- I have just completed a course in advanced C++ at the University of California
- Extension Division. In the last class, we discussed new features of C++
- including namespaces. That discussion brought up the fact that when the
- directive form is used (i.e. "using namespace") all classes, variables and
- functions declared in that namespace will be put into the global scope. It
- seems that this could cause problems if there were already items with the same
- name in global scope.
- Is it possible to do some sort of name mangling on the variables etc. in the
- namespace when the using namespace declaration is used so that these items
- retain their uniqueness and thus can be accessed by their symbolic name only?
- The items already existing at the global scope can then be accessed by using
- the scope resolution operator.
- Also, when do the namespace items get removed from global scope? Is it
- delimited by a { } pair or something else?
- Thanks,
- Dave Goldstein
- Pittsburgh, PA
- I believe there are rules that favor items declared in the parent namespace
- over those imported via a using declaration, but you're still correct.
- Programmers will encounter conceptual problems when names collide. Sometimes
- they'll expect to get the imported name, sometimes they'll expect to get the
- native name, and sometimes they'll want a diagnostic. Since no commercial
- compilers exist that implement namespaces, to my knowledge, it's hard to do
- experiments to see what works and what doesn't.
- As for your last question, like any other declaration, the names it introduces
- go out of scope at the end of the block containing the declaration.
- Thanks for writing,
- P.J. Plauger
- Hi
- We are New at Visual C++ and have a problem.
- I need to create a modal dialog, that has no resource template, because I read
- the window's size and position from an ASCII file, as well as a fields
- definition -- these fields are created dynamically.
- When I am building the above-mentioned window, I already have another one
- which is modal. I need to disable the first one and then show the window I am
- putting up from the ASCII file.
- For creating the window I used the CreateExt function, This window is derived
- from CWnd instead of CDialog.
- Can you gimme a clue as to how to create this window?
- I send you my regards.
- Juan Carlos Flores
- (jflores@macosa.com.ec)
- MACOSA - NCR
- Alpallana 289 y Almagro
- Quito - Ecuador South America
- Veronica Burbano
- (vburbano@macosa.com.ec)
- MACOSA - NCR
- Alpallana 289 y Almagro
- Quito - Ecuador South America
- Any Windows gurus out there? -- pjp
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- WinJES -- A Windows Sockets Example
-
-
- Kevin Gilhooly
-
-
- Kevin Gilhooly is with RIS Information Services in Dallas, working on a
- variety of PC-based client server projects. He can be reached at kjg@dfw.net.
-
-
-
-
- Introduction
-
-
- Windows Sockets is an open network programming interface for Microsoft
- Windows. Adapted from Berkely Sockets (which works with UNIX systems), Windows
- Sockets enables two programs to establish a bidirectional connection. The
- programs may be on the same or different computers.
- In this article I present WinJES, a small Microsoft Windows program that lets
- a user issue a command on a remote Windows machine. WinJES uses the Windows
- Sockets interface for communications between PCs across a network. The program
- is both a client and a server, and one copy can exchange messages either with
- itself or with a copy on a different machine.
- I wrote the original WinJES program using Microsoft's Visual C++ 1.1 and
- NetManage's Chameleon SDK under Windows for Workgroups 3.11. The program uses
- the Chameleon TCP/IP stack. I've also compiled it under the Windows NT 3.5
- Workstation's native TCP/IP, with Visual C++ version 2.0. Any
- Windows-Sockets-compliant developer's kit and Windows C compiler should be
- able to compile the source code.
-
-
- Establishing a Protocol
-
-
- To communicate with each other, two programs must use a protocol. Windows
- Sockets programs use either the Transmission Control Protocol (TCP) or User
- Datagram Protocol (UDP) (see sidebar, "Understanding Sockets and Protocols.").
- WinJES uses UDP. Unlike TCP, two machines communicating via UDP do not
- establish a "connection." Rather, they send messages back and forth without
- acknowledgements. When WinJES starts, it listens for requests from other
- machines. A user on a remote machine can then submit commands to be executed
- on the local machine. In many environments, a program can simply wait for
- input from a remote partner. In UNIX, or on a dedicated DOS machine, a
- standard UDP server program with no error checking would look like Listing 1.
- For various reasons, a Windows 3.x server cannot be quite so simple. For one
- thing, because Windows 3.x is non-preemptive, programs must yield the
- processor to Windows after a reasonable time, or no other processes will be
- able to run. Also, Windows is based on a messaging model. When any event
- occurs, Windows sends a message to a window procedure (code tied to a
- particular window); the code must process the message as quickly as possible
- and return to a passive state. Therefore, the above code must be modified for
- Windows.
- WinJES does the real work inside a message loop. Before entering a message
- loop, the program must register a window class and create a window. The window
- class ties the window to a window procedure (the code that processes its
- messages.) Since WinJES uses the Windows Sockets functions, it must initialize
- Windows Sockets at the start of the program (via function WSAStartup), and
- terminate Windows Sockets when the program exits (via function WSACleanup).
- Listing 2 shows the main routine for WinJES, which creates the main window,
- and enters the message loop.
-
-
- Processing Windows Messages
-
-
- In Windows, a window procedure processes Windows messages dispatched from the
- message loop. To create a window procedure for WinJES, I've added processing
- for specific messages that I care about. All other messages go to
- DefWindowProc, the default Windows procedure. The window procedure is shown in
- Listing 3. A description of the critical messages processed follows:
- In the WM_CREATE message:
- open a socket()
- bind() it to an address
- WSAAsyncSelect() defines a message and events to filter
- In the WM_CLOSE or WM_DESTROY message:
- close the socket
- In the defined socket message:
- switch on wParam (a Windows message parameter) to determine the socket
- switch on the low word of lParam (another Windows message parameter) to
- determine the event
- This is a standard model for Windows Sockets servers. WSAAsyncSelect links a
- list of events on a specific socket to a window and establishes the message to
- send to that window when one of the events occurs. When the message is posted,
- the window procedure can look at the low word of lParam to determine the
- specific event that occurred. The specific socket ID is passed as the wParam.
- This scheme allows a single message routine to service multiple requests on
- multiple sockets.
- The window procedure for the WinJES program is very simplistic. The user
- interface is a single-line display, filled from a global variable in response
- to the WM_PAINT message. I've added the menu item to submit commands to other
- WinJES servers to the Windows system menu, so the WinJES applicaton will not
- need a menu of its own.
- From a communications standpoint, the most important message is WM_SOCKET, a
- user-defined message which is defined in the file WINJES.H (on this month's
- code disk). All private messages must be defined above WM_USER (a constant in
- WINDOWS.H). The WM_SOCKET message is referenced in WSAAsyncSelect, so the
- Windows Sockets Dynamic Link Library (DLL) posts it when a socket event
- occurs.
- Because of the Intel architecture, a Windows 3.1 program must deal with a
- two-part memory address: segment and offset. Handling the address correctly is
- critical when passing parameters to code outside the program. All the
- parameters passed from a Windows program to the Windows Sockets DLL routines
- must either be FAR or cast as a FAR when passed. Copying memory also requires
- care. In a UNIX or Windows NT program, the usual way to copy from one address
- to another is memcpy. In Windows 3.1 or Workgroups, programs must use
- _fmemcpy, which is an address-independent version. It will correctly handle
- far pointers.
-
-
- WinJES Server Processing
-
-
- The server portion of WinJES waits for a message to arrive, then receives and
- parses it. The server extracts the command parameter from the structure, and
- passes it (untouched) to the WinExec API for execution. WinExec takes two
- parameters, a command string and a visibility option. WinJES runs all programs
- as SW_NORMAL, which means they are visible on the screen and receive focus
- when they start. The command string may be a Windows or DOS program name, plus
- any parameters the program needs.
-
-
- Preparing a socket for input
-
-
- All socket programs begin by allocating a socket for use. A server continues
- by binding the socket to a specific port, and monitoring the port for
- messages. This process occurs in response to a Windows WM_CREATE message. (The
- code is shown under the WM_CREATE case of the window procedure's outermost
- switch statement.) A Windows Sockets program listens by asking the Windows
- Sockets DLL to post a message whenever a defined event occurs. WinJES asks the
- DLL to post a WM_SOCKET message whenever the socket is ready to read. "Ready
- to read" means that the program can perform a receive on the socket and the
- function will not hang. In this UDP program, "ready to read" is the only event
- a server cares about.
-
-
-
- Receiving a message
-
-
- When the socket receives data, the Windows Sockets DLL posts a WM_SOCKET
- message, as requested in the WSAAsyncSelect call. (Refer to the WM_SOCKET case
- clause in the window procedure.) The server can look at the long parameter's
- low word to determine the event type. In WinJES, the only critical event is
- FD_READ, which occurs when a request is ready for processing. The parameter
- will match one of the events defined in the WSAAsyncSelect call. Only those
- requested events will trigger the message to the window.
-
-
- WinJES Client Processing
-
-
- Sending a UDP message is very simple: grab a socket, point it at the remote
- machine's address and the program's port, and toss it out on the network. A
- UDP client will not know if the message arrives unless the server program
- acknowledges it. In WinJES, a UDP message is sent in response to a WM_COMMAND
- message generated by a menu selection. (Refer to case IDM_SEND under case
- WM_COMMAND in the main window procedure.) This code fragment will work for
- many command-line clients, such as a UNIX machine, by moving it and
- recompiling it almost as is (removing Windows-specific calls). The UNIX WinJES
- client would receive the host name as a command-line parameter and use gets to
- read the command to issue, and then runs the (nearly) identical socket code
- for communications.
-
-
- Conclusion
-
-
- The Windows Sockets interface is a powerful addition to basic Windows
- programming. It extends Windows programs to provide communications over an
- internet easily, while providing a reasonable porting path for TCP/IP code
- from other platforms.
- Understanding Sockets and Protocols
-
-
- What are Windows Sockets?
-
-
- The Windows Sockets API and interfaces is an open network programming
- interface for Microsoft Windows. The Windows Sockets API is implemented by
- multiple TCP/IP vendors for Windows. Sockets are an abstraction, a method of
- communications between programs using the TCP/IP protocol stack. A socket is a
- bidirectional connection between two programs, which may be running on the
- same or different computers. The Windows Sockets interface is an adaptation of
- Berkeley sockets (as found in many UNIX variants), revised to perform politely
- in a Windows environment. The Windows Sockets dynamic link library (DLL) is
- responsible for notifying a program when a socket needs attention.
-
-
- UDP and TCP
-
-
- Socket-based programs are asynchronous, and messages can arrive at any time. A
- program defines its own protocol to determine what messages are valid from a
- partner (or proposed partner) at any time.
- Windows Sockets use either TCP (Transmission Control Protocol) or UDP (User
- Datagram Protocol.) TCP provides reliable, two-way, connection-based
- communications, while UDP provides connectionless, "unreliable"
- communications. TCP programs are much like a phone call -- once a connection
- is made, the programs converse, knowing the partner is there. UDP is like
- mailing a letter -- you know the message was sent, but you have no way of
- knowing it arrived (unless your partner sends you another message to confirm.)
- I have always considered the word "unreliable" a bit excessive; on a local
- network, I have never lost a UDP packet.
-
-
- TCP/IP Addresses
-
-
- Regardless of the protocol used (TCP or UDP), Windows Sockets uses the TCP/IP
- addressing scheme. TCP/IP uses a 32-bit address to identify a machine. The
- machine address is usually expressed in dotted decimal notation, for example,
- my machine address at work is 198.63.66.15. Since multiple programs can
- execute simultaneously on the same machine, each program is assigned a port
- for connections. Some programs (such as mail or file transfers) have
- well-known ports, which are the same on all machines running TCP/IP. Other
- ports are available for user programs. Port numbers below 1,024 are reserved
- for well-known ports. In many UNIX implementations, ports below 4,096 are
- reserved for programs running with root privilege. User-defined ports must be
- at or below port number 65,535. Usually the server will define the port
- number.
- Because people deal with names more easily than numbers, TCP/IP has facilities
- to give machines names as well as numeric addresses. The simplest method is a
- hosts file, which is a list of addresses with the corresponding names. Larger
- installations use DNS (Domain Name Services), which is a centralized method of
- resolving names into valid addresses. A DNS server is a machine on the network
- that runs DNS and responds to name-to-address translation requests.
- WinJES uses the Windows Sockets call gethostbyname to translate names into
- address information. A program can find the name of the machine it's on by
- calling gethostname. Many server programs call gethostname to avoid hardcoding
- the machine name or address in the code. The Windows Sockets library routines
- are usually written to look up names in the local hosts file first, and then
- go to a DNS server to resolve a name. However, different implementations have
- different defaults, so you should check the vendor notes to find the specific
- search order your implementation uses.
-
-
- UDP Servers
-
-
- A UDP server waits for messages to arrive on a predetermined port, and then
- processes the message. The contents of the message are specific to the server
- program. When a message arrives, the server uses recvfrom to retrieve the
- message and the address of the machine that sent it. To send a reply to the
- client, a UDP server can call sendto, passing the same socket address that the
- recvfrom call returned.
- A UDP server does not usually have a conversation with a client. UDP is a good
- protocol for inbound messages that will cause the server to perform an action.
- Since delivery is not guaranteed, it is possible for a server to miss a
- request. If message loss cannot be tolerated, or you need a two-way
- (request/reply) conversation, use TCP instead.
-
-
- TCP Servers
-
-
- A TCP server listens for clients to connect to its port. Once a client
- connects, the server program accepts the conversation. The accept call returns
- a socket which the server uses to converse with the client. The server's
- original socket is returned to a listening state for more inbound connections.
- In Windows, a TCP server can create a new window to deal with each inbound
- connection. The new window would deal with the client that connected, and the
- server window would continue to listen for more requests.
-
-
- Inter-machine Communications
-
-
- Because different machines store integer data differently, the Windows Sockets
- library provides routines to translate from local form to a known format and
- back. Before transferring integers across the network, a program can call
- htons (Host-to-Network-Short) for 16-bit values or htonl
- (Host-To-Network-Long) for 32-bit values to receive a network-neutral value.
- The recepient then calls ntohs or ntohl to translate the data to its local
- form. It is important to know how your C compiler defines an int, a short, and
- a long in bytes, since it varies by platform.
-
-
-
- The WinJES Protocol
-
-
- WinJES has a very simple protocol defined on top of UDP datagrams. The
- datagram must contain the following WinJES command structure:
- typedef struct _Message
- {
- char szCommand[MAXBUFFER];
- char szHostName[MAXHOST];
- } Message;
- The WinJES server listens for messages to arrive on UDP port 10000. The port
- number is defined in the WINJES.H file. WinJES servers expect a client to send
- a single datagram to port 10000.
-
- Listing 1 A UDP server that would work on UNIX systems
- get a socket()
- bind() it to an address
- while ( recvfrom())
- {
- process the incoming message
- }
-
-
- Listing 2 The WinJES main program
- #define GLOBALS TRUE
- #include "winjes. h"
- #undef GLOBALS
-
- int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
- LPSTR lpCmdLine, int nCmdShow)
- {
- MSG msg;
-
- if (!hPrevInstance)
- if (!InitApplication(hInstance))
- return (FALSE);
-
- if (!InitInstance(hInstance, nCmdShow))
- return (FALSE);
- while (GetMessage(&msg, NULL, NULL, NULL))
- {
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
-
- return (msg.wParam);
- }
-
- BOOL InitApplication(hInstance)
- HANDLE hInstance;
- {
- WNDCLASS wc;
-
- wc.style = CS_DBLCLKS;
- wc.lpfnWndProc = MainWndProc;
- wc.cbClsExtra = 0;
- wc.cbWndExtra = 0;
- wc.hInstance = hInstance;
- wc.hIcon = LoadIcon(hInstance, "TellMe");
- wc.hCursor = LoadCursor(NULL, IDC_ARROW);
-
- wc.hbrBackground = GetStockObject(WHITE_BRUSH);
- wc.lpszMenuName = NULL;
- wc.lpszClassName = "jesWClass";
-
- return (RegisterClass(&wc));
-
- }
-
- BOOL InitInstance(HANDLE hInstance, int nCmdShow)
- {
- HWND hWnd;
-
- hInst = hInstance;
-
- hWnd = CreateWindow("jesWClass",
- "WinJES",
- WS_OVERLAPPED WS_CAPTION
- WS_SYSMENU WS_MINIMIZEBOX,
- CW_USEDEFAULT,
- CW_USEDEFAULT,
- 70,
- 50,
- NULL,
- NULL,
- hInstance,
- NULL);
-
- if (!hWnd)
- return (FALSE);
-
- ShowWindow(hWnd, nCmdShow);
- UpdateWindow(hWnd);
- return (TRUE);
-
- }
-
- /* End of File */
-
-
- Listing 3 The WinJES window procedure
- #undef GLOBALS
- #include "winjes.h"
- /*
- Routine: MainWndProc
- Called By: Windows
- Usage: This is the window procedure for the main window
- */
-
- long FAR PASCAL MainWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
- lParam)
- {
-
- BOOL rc;
- char szName[MAXNAME+1];
- FARPROC lpProc;
- HDC hDC;
- HMENU hMenu;
- int n;
- int len;
- int s;
-
- PAINTSTRUCT ps;
- POINT ptClick;
- RECT rect;
- struct hostent FAR *hp;
- struct sockaddr_in sin;
- WORD wVer;
- WSADATA wsData;
- switch (message)
- {
- case WM_CREATE:
- wVer = 0x101; /* version 1.1 */
- rc = WSAStartup(wVer, (LPWSADATA)&wsData);
- sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
- gethostname(szName, MAXNAME);
- hp = gethostbyname((LPSTR)szName);
- if (hp == NULL)
- lstrcpy(szMsg, (LPSTR)"error");
- else
- {
- sin.sin_family = AF_INET;
- sin.sin_port = htons(MYPORT);
- _fmemcpy((struct sockaddr_in FAR *)&sin.sin_addr,
- (struct hostent FAR *)hp->h_addr, hp->h_length);
- n = bind(sock, (struct sockaddr FAR *)&sin, sizeof(sin));
- if (n < 0)
- wsprintf((LPSTR)szMsg, (LPSTR)"bind: %d", n);
- else
- {
- WSAAsyncSelect(sock, hWnd, WM_SOCKET, FD_READ FD_CONNECT FD_CLOSE);
- lstrcpy(szMsg, (LPSTR)"ready");
- }
- }
- InvalidateRect(hWnd, NULL, TRUE);
- /*
- add Tell... to the system menu (so we can get to it even when minimized)
- */
- hMenu = GetSystemMenu(hWnd, FALSE);
- InsertMenu(hMenu, 0, MF_BYPOSITION, IDM_SEND, (LPSTR)"&Sub...");
- InsertMenu(hMenu, 1, MF_BYPOSITION MF_SEPARATOR, (UINT)-1, (LPSTR)"");
- break;
-
- case WM_SOCKET:
- switch (LOWORD(lParam))
- {
- case FD_READ: // new data/receive queue full
- len = sizeof(sin);
- n = recvfrom(sock, (LPSTR)(Message far *)&InMessage, sizeof(Message), 0,
- (struct sockaddr FAR *)&sin, (int FAR *)&len);
- if (n < 0)
- wsprintf(szMsg, (LPSTR)"err: %d", n);
- else
- {
- if (!lstrcmpi((LPSTR)InMessage.szCommand, (LPSTR)"reboot"))
- ExitWindows(EW_REBOOTSYSTEM, 0);
- if (n = WinExec((LPCSTR)InMessage.szCommand, SW_NORMAL) < 32)
- wsprintf(szMsg, (LPSTR)"rc = %d", n);
- else
- lstrcpy(szMsg, (LPSTR)"command");
- }
-
- break;
-
- case FD_CLOSE: // connection closed
- lstrcpy(szMsg, (LPSTR)"closed");
- break;
-
- case FD_CONNECT: // connection request
- lstrcpy(szMsg, (LPSTR)"connect");
- break;
- }
- InvalidateRect(hWnd, NULL, TRUE);
- break;
-
- case WM_KEYDOWN:
- if (wParam == VK_HOME)
- PostMessage(hWnd, WM_RBUTTONDOWN, (WPARAM)0, ,MAKELPARAM(0, 0));
- break;
-
- case WM_RBUTTONDOWN:
- hMenu = CreatePopupMenu();
- AppendMenu(hMenu, MF_STRING, IDM_SEND, (LPSTR)"&Sub...");
- AppendMenu(hMenu, MF_SEPARATOR, (UINT)-1, (LPSTR)"");
- AppendMenu(hMenu, MF_STRING, IDM_ABOUT, (LPSTR)"&About");
- ptClick = MAKEPOINT(lParam);
- ClientToScreen(hWnd, &ptClick);
- TrackPopupMenu(hMenu, TPM_LEFTALIGN, ptClick.x, ptClick.y, 0, hWnd, NULL);
- InvalidateRect(hWnd, NULL, TRUE);
- DestroyMenu(hMenu);
- break;
-
- case WM_COMMAND:
- switch(wParam)
- {
- case IDM_SEND:
- lpProc = MakeProcInstance(HostProc, hInst);
- rc = DialogBox(hInst, (LPSTR)"Command", hWnd, lpProc);
- FreeProcInstance(lpProc);
- if (!rc)
- break;
- s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
- hp = gethostbyname((LPSTR)OutMessage.szHostName);
- if (hp == NULL)
- {
- lstrcpy(szMsg, (LPSTR)"Invalid Host");
- closesocket(s);
- }
- else
- {
- sin.sin_family = AF_INET;
- sin.sin_port = htons(MYPORT);
- _fmemcpy((struct sockaddr_in FAR *)&sin.sin_addr,
- (struct hostent FAR *)hp->h_addr, hp->h_length);
- gethostname((LPSTR)OutMessage.szHostName, MAXHOST-1);
- sendto(s, (LPSTR)(Message far *)&OutMessage, sizeof(Message), 0,
- (struct sockaddr FAR *)&sin, sizeof(sin));
- closesocket(s);
- lstrcpy((LPSTR)szMsg, (LPSTR)"outgoing");
- }
- InvalidateRect(hWnd, NULL, TRUE);
-
- break;
-
- case IDM_ABOUT:
- lpProc = MakeProcInstance(AboutProc, hInst);
- rc = DialogBox(hInst, (LPSTR)"About", hWnd, lpProc);
- FreeProcInstance(lpProc);
- break;
- }
- break;
-
- case WM_SYSCOMMAND:
- switch(wParam)
- {
- case IDM_SEND:
- PostMessage(hWnd, WM_COMMAND, IDM_SEND, 0L);
- break;
-
- default:
- return (DefWindowProc(hWnd, message, wParam, lParam));
- break;
- }
- break;
-
- case WM_PAINT:
- hDC = BeginPaint(hWnd, &ps);
- GetClientRect(hWnd, &rect);
- DrawText(hDC, (LPSTR)szMsg, -1, &rect, DT_SINGLELINE DT_CENTER DT_VCENTER);
- EndPaint(hWnd, &ps);
- break;
-
- case WM_CLOSE:
- DestroyWindow(hWnd);
- break;
-
- case WM_DESTROY:
- closesocket(sock);
- WSACleanup();
- PostQuitMessage(0);
- break;
-
- default:
- return (DefWindowProc(hWnd, message, wParam, lParam));
- break;
- }
-
- return FALSE;
- }
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Dynamic Client/Server-Based Image Processing
-
-
- Paul Colton
-
-
- Paul Colton is a Software Engineer for Inter-National Research Instititute,
- Inc. in San Diego, California. He has over five years programming experience
- and enjoys developing computer graphics software including 3-D rendering
- systems. He runs an Internet-based publishing and software distribution
- company, MiNET, at http://www.minet.com/minit/. Paul can be reached via e-mail
- at pc@minet. com.
-
-
-
-
- Introduction
-
-
- Most imaging systems need to perform complex CPU-intensive image processing
- functions on data. These computations tax the software a great deal and often
- result in the decrease of overall software performance. As a remedy, a
- client/server model is useful in distributing the work.
- In the client/server model with Remote Procedure Calling (RPC), imaging
- programs can be divided into two sections, the client and the server. The
- client takes care of the users' needs, the user interface, file I/O, and any
- other user-specific items. The server performs the image processing
- operations. This division of labor lets the client CPU focus on the user while
- the server CPU focuses on the actual work. Multiple clients can connect to the
- same server, making each of the clients smaller in code size. Because the
- clients and server need not run on the same hardware, the server can benefit
- from a dedicated machine. If the server is running on a much faster platform
- than the clients', each client will attain most of the image processing speed
- of the server to which it is connected. Also, this configuration allows for
- file sharing of data between the clients.
- This article describes the implementation of such a client/server-based image
- processing system. Furthermore, this article shows how to establish a dynamic
- relationship between the client and the server, and how to design the server
- for maximum expandability.
-
-
- The Client/Server Model and RPC
-
-
- The client/server model for computing is a popular method for distributed
- processing. This model allows processes to make requests for a task while a
- separate process actually does the work. Interprocess communication (IPC) is
- an essential part of the client/server model. IPC allows two processes to
- communicate and exchange data. Unfortunately, IPC is difficult to develop,
- debug, and use. Also, it tends to be machine-dependent.
- Remote Procedure Calling (RPC) was developed so programmers would not have to
- work at this level of complexity. RPC hides the details of IPC and makes the
- development of applications much easier as well as very portable. RPC allows
- local client applications to execute procedures on remote servers. Then the
- servers can send the results of the procedure back to the client.
-
-
- The Image Processing Server (IPS)
-
-
- The dynamics of the IPS are rooted in the server application. The IPS has
- three main functions:
- 1. To receive an image from a client and decode it into a native format
- 2. To perform image processing functions on that native format
- 3. To return the image to the client in a specified format
- The server does not do the decoding, image processing, and encoding directly,
- but calls upon three banks of supporting programs: loaders, savers, and
- operators. These programs are separate, self-contained executables that
- respectively load, save, and perform operations on images. The server source
- code does not contain any specific information about which image formats are
- supported directly. The source code only knows where the loaders, savers, and
- operators are physically located. The client is responsible for telling the
- server the format of the incoming data. The server will execute the proper
- image loader, if available, to convert the image into an internal format. This
- protocol also applies to the savers and operators.
-
-
- The Image-Processing Client
-
-
- The image-processing client is responsible for sending the image data to the
- server along with the image format. For example, if the client sends an
- X-Window Dump (XWD) image to the server, the server then checks if the XWD
- loader exists. If so, the server executes the XWD loader using the client file
- as the input parameter and a temporary file as the output parameter. The
- server gives a unique id to the new converted image and returns that id to the
- client. When the client needs to perform image-processing operations on this
- image, the client need only send the image-processing command and the image id
- to the server. When the client has completed sending image-processing
- operations to the server, it can request that the image be sent back in its
- final form. Again, the client sends an image format, and the server executes
- the proper image saver.
-
-
- Dynamic Communications
-
-
- Neither the server nor the client knows which loaders, savers, or operators
- are available. That is, the source code contains no references to any image
- formats or image-processing functions. Here is where the dynamics of this
- system come into play. The system remains very open, indeed almost infinitely
- expandable, by introducing only new loaders, savers, and operators, and by
- never modifying the original server source code.
- The client and server can communicate with each other to determine what is
- available. The client asks the server for three lists: the loaders available,
- the savers available, and the operators available. The server then checks its
- predetermined loaders, savers, and operators directories and responds with the
- lists. Now the client is fully aware of what image formats and operators are
- available and can make this information available to the user for selection.
- Furthermore, a unique type of loader called an AUTO-DETECT image loader can be
- written. This executable does not load an image directly, but auto-detects the
- image format and then call the proper loader, freeing the client from knowing
- the format of the image, and consequently letting the client operate without
- having to know what loaders are available from the server.
-
-
- Implementation
-
-
- Developing any RPC applications first requires a communications protocol file.
- This file is written in the ONC RPC Language (RPCL), a C-style language that
- details how the client and server will exchange data. A protocol compiler
- inputs this file and generates the client and server stubs in C. I use the ONC
- RPCGEN protocol compiler. Listing 1 shows the protocol for the Image
- Processing Server, or IPS. To compile this protocol, use the following
- command:
- prompt> rpcgen ips.x
- RPCGEN will generate the client stub, ips_clnt.c, and the server stub,
- ips_svc.c. It will also produce an XDR filter file, ips_xdr.c, as well as a
- header file, ips.h. To compile all of the pieces use the following commands:
- To compile the client stub:
-
- prompt> cc -c ips_clnt.c
- To compile the server stub:
- prompt> cc -c ips_svc.c
- To compile the XDR filters:
- prompt> cc -c ips_xdr.c
- You should place all of these files in one directory. I chose the name ips as
- my directory name.
- Now that the RPC stubs have been generated and compiled, you must create and
- compile the actual client and server applications, and put all of the pieces
- together. The example server in this article allows clients to perform several
- functions.
- The server must first allow clients to connect to it. Upon connection, the
- server will return a welcome message to the client. Now the client can request
- the lists of loaders, savers, and operators, and it can also send and receive
- images from the server. When the server receives an image file, it returns a
- unique id to the client. The client uses this id to access the image for
- image-processing operations as well as to receive the image back upon
- completion of the processing functions.
- The main function in the server source code is ips_1. This function has a stub
- in the client source code. When the client sends a request to the server via
- ips_1, the proper functions are called via a switch statement in the server
- code. Listing 2 shows the source code for the server process. To compile this
- program, use the following command (broken to fit column):
- prompt> cc -o ips ips.o ips_svc.o
- ips_xdr.o
- The server also requires that a few environment variables be set. These
- environment variables tell the server where the loaders, savers, and operators
- are located, as well as what directory to use for a temporary storage area.
- You must set the following case-sensitive variables prior to running any
- portion of this software:
- LOADERS_DIR -- the absolute path name of the loaders directory
- SAVERS_DIR -- the absolute path name of the savers directory
- OPS_DIR -- the absolute path name of the operators directory
- STORAGE_DIR -- the absolute path name of the temporary storage directory
- I created four directories at the same level as the above-mentioned ips
- directory; they are loaders, savers, operators, and temp. You must set all of
- the above environment variables to these directories. To run the server,
- simply type the name of the server program -- in the case ips. If rou are
- using X-Window, you can run the server in its own window. For example, you
- might use:
- prompt> xterm -e ips &
- This would allow you to view the progress of the server without its output
- interfering in your window. If you're running the server on a single window
- system, you can redirect the server's output to a file or to a null device,
- such as /dev/null.
- Listing 3 shows the client program. This program shows only one example of
- what can be done and does not exhaust all possibilities. For the sake of
- clarity and size, I use no graphic interface, but a simple ASCII menuing
- system. This program allows a user to send an image, receive an image, get the
- list of loaders, savers, and operators, and execute an operator on the current
- image in the server. The server is not limited to storing a single image, but
- this client only demonstrates the use of a single image at a time.
- When you execute the client, it will display the welcome message from the
- server and then proceed to prompt you for input. Entering a question mark will
- display a menu of all available options. When you send image-format type
- information or an operator name, be sure to type it exactly as it is shown
- from the list of available items. (Don't type "xwd" for "XWD".) To compile the
- demo client program, use the following command (broken to fit column):
- prompt> cc -o demo_client demo_client.o
- ips_clnt.o ips_xdr.o
- This client program should reside in the same directory as the server. To run
- the program, make sure the server is already running, then just type
- demo_client followed by the hostname of the machine that is running the
- server.
- Finally, you will need the supporting programs -- the loaders, savers, and
- operators. The loaders, savers, and operators need to agree on an internal
- format, but since they are the only ones using this format, it can be changed
- at any time without recompiling the server or client. As a further advantage,
- the clients and server can be running while you are adding or changing these
- programs. In this article, the internal format is simply implemented as a
- header containing any needed information like number of colors and image size,
- followed by the actual uncompressed image data. Listing 4 shows the header for
- the internal format. All of the supporting programs use this header.
- The loader, saver, and operator all use a small library to keep the code size
- short. This library (Listing 5) contains some convenience routines for loading
- the image data and writing the image data back to a file. The library should
- be linked with the loader, saver, and operator code. To compile the library,
- use the following commands:
- prompt> cc -c fileio.c
- prompt> ar rc libips.a fileio.o
- prompt> ranlib libips.a (for SunOS)
- I have supplied two example loaders for this article, XWD and AUTO-DETECT.
- Listing 6 shows the X-Window (XWD) image-format loader. You should put XWD in
- a different directory than the ips directory, such as loaders_source, along
- with the ips_header.h file. To compile this program, use the following
- command:
- prompt> cc -o XWD xwd.c libips.a
- Move the compiled version of this program into the same directory pointed to
- by the LOADERS_DIR environment variable.
- Listing 7 shows source code for the second loader, AUTO-DETECT. AUTO-DETECT
- demonstrates the use of an auto-detect file format. It supports the detection
- of XWD and GIF images. Put this source code in the same directory as the XWD
- loader source. To compile this program, use the following command:
- prompt> cc -o AUTO-DETECT auto-detect.c
- Move the compiled version of this loader into the LOADERS_DIR directory.
- Listing 8 shows an example file saver for XWD format. To compile the source
- code, follow the same instructions that you used to compile the XWD loader.
- Place this source in a savers_source directory, and then move the executable,
- named XWD, to the same directory as indicated by the SAVERS_DIR environment
- variable. (Note that the XWD loader and saver executables will have the same
- filenames.)
- I include here the source code for one example image operator. Listing 9 shows
- the operator called FLIP-Y. This operator will flip an image vertically. Place
- the source in a operators_source directory. This source can be compiled like
- the others, but you should call the executable FLIP-Y and place it in the same
- directory pointed to by the OPS_DIR environment variable.
- You now have all of the pieces you need to run both the image processing
- server and the demo client. As a test of the system, grab an X-Window Dump
- image via the xwd program and try sending it to the server and getting it
- back. If the server is not responding, make sure all of the above steps have
- been executed properly. Be sure that the image filename you supply to the
- client does not contain any directory information. The file should be located
- in the current directory.
-
-
- Extending the Model
-
-
- I've shortened the code listed in this article for clarity, but many options
- can be added to the server for even greater functionality. One issue I haven't
- addressed is how to handle loaders, savers, or operators that need more
- information. For example, a SCALE function would need to know the new size of
- the image. One approach is to develop a protocol between the client, server,
- and the loaders, savers, or operators. This protocol would allow the client to
- ask the server what arguments are needed by the program in question; then the
- client could prompt the user for this information. The client would pass the
- final command to the server with the arguments included.
- The server included in this article already allows for arguments to be passed
- to the savers and the operators. This option is useful for savers like JPEG,
- which requires that the user enter a compression ratio.
- The AUTO-DETECT loader can easily be expanded to include new file formats.
- This system need not be limited to imagery. In fact, the server can handle any
- bit-oriented data. The server is indifferent to the data coming across; it
- only passes the data along to the supporting programs. You can easily add
- digital audio, video, and other formats to this system.
-
-
- Conclusion
-
-
- This dynamic model of a client/server system for image processing opens many
- possibilities: having slower systems harness the power of a faster system on a
- network, sharing files among clients on the network, distributing the image
- processing among multiple servers for improving performance, allowing the
- clients to concentrate the CPU on the user interface, and allowing multiple
- platforms to share the image-processing power of a single server. With some
- creativity, this system can become a very powerful and valuable tool in image
- processing and data handling in general.
-
-
- Bibliography
-
-
- Bloomer, J. Power Programming with RPC. O'Reilly & Associates, 1991.
-
-
- Listing 1 Image processing server (IPS) protocol
- /*
- * Listing 1 - ips.x
- */
-
- /**** Client/Server Messages ****/
-
- const IPS_DECODE = 0;
- const IPS_OK_ID = 1;
- const IPS_UNSUPPORTED_FORMAT = 2;
- const IPS_UNKNOWN_FORMAT = 3;
-
- const IPS_REQUEST_OPS = 4;
- const IPS_SEND_OPS = 5;
-
- const IPS_REQUEST_LOADERS = 6;
- const IPS_SEND_LOADERS = 7;
-
- const IPS_REQUEST_SAVERS = 8;
- const IPS_SEND_SAVERS = 9;
-
- const IPS_REQUEST_IMAGE = 10;
- const IPS_SEND_IMAGE = 11;
-
- const IPS_PROCESS = 12;
- const IPS_PROCESS_OK = 13;
-
- const IPS_IMAGE_RELEASE = 14;
- const IPS_RELEASE_OK = 15;
- const IPS_IMAGE_NOT_AVAIL = 16;
-
- const IPS_INIT = 17;
- const IPS_HELLO = 18;
-
- const ERROR = 255;
-
- /******** Image/Text Sizes ********/
-
- const MAXPIX = 4194304;
-
- /********** Structures ************/
-
- struct raw_data {
- string filename<>;
- string format<>;
- opaque data<MAXPIX>;
- };
-
- struct converted_data {
- opaque data<MAXPIX>;
- };
-
- struct list {
- string name<>;
- struct list *next;
- };
-
- struct proc_data {
- int id;
-
- string command<>;
- int argc;
- list argy;
- };
-
- struct request_send {
- int id;
- string format<>;
- int argc;
- list argv;
- };
-
- /********* Main Stuff ***********/
-
- union Packet switch (int op) {
- case IPS_INIT: void;
- case IPS_HELLO: string hello_string<>;
-
- case IPS_DECODE: raw_data raw_img;
- case IPS_OK_ID: int id;
- case IPS_UNSUPPORTED_FORMAT: void;
- case IPS_UNKNOWN_FORMAT: void;
-
- case IPS_REQUEST_OPS: void;
- case IPS_SEND_OPS: list operators;
-
- case IPS_REQUEST_LOADERS: void;
- case IPS_SEND_LOADERS: list loaders;
-
- case IPS_REQUEST_SAVERS: void;
- case IPS_SEND_SAVERS: list savers;
-
- case IPS_REQUEST_IMAGE: request_send send_ops;
- case IPS_SEND_IMAGE: converted_data img;
-
- case IPS_PROCESS: proc_data proc;
- case IPS_PROCESS_OK: void;
-
- case IPS_IMAGE_RELEASE: int place_holder;
- case IPS_RELEASE_OK: void;
- case IPS_IMAGE_NOT_AVAIL: void;
-
- case ERROR: void;
- default: void;
- };
-
- program IPSPROG {
- version IPSVERS {
- Packet IPS(Packet) = 1;
- } = 1;
- } = 0x20000001;
- /* End of File */
-
-
- Listing 2 The server process source code
- /*
- * Listing 2 - ips.c
- */
-
-
- #include <stdio.h>
- #include <string.h>
- #include <rpc/rpc.h>
- #include <sys/dir.h>
- #include <unistd.h>
- #include "ips.h"
-
- /* Constants */
- #define STORAGE_DIR getenv("STORAGE_DIR")
- #define LOADERS_DIR getenv("LOADERS_DIR")
- #define SAVERS_DIR getenv("SAVERS_DIR")
- #define OPS_DIR getenv("OPS_DIR")
-
- /* Prototypes */
- void ips_hello();
- void ips_decode();
- void ips_process();
- void ips_encode();
- void ips_release();
- void ips_request_list();
- void ips_request_args();
- char *replace_ext( );
- char *get_filename();
- char *upcase();
- int get_next_id();
- unsigned char *loadfile();
-
- Packet *
- ips_1(request)
- Packet *request;
- {
- static Packet reply;
- char sbuf[1024];
-
- xdr_free(xdr_Packet, reply);
-
- switch(request->op) {
- case IPS_INIT:
- ips_hello(request, &reply);
- break;
-
- case IPS_DECODE:
- ips_decode(request, &reply);
- break;
-
- case IPS_REQUEST_IMAGE:
- ips_encode(request, &reply);
- break;
-
- case IPS_PROCESS:
- ips_process(request, &reply);
- break;
-
- case IPS_IMAGE_RELEASE:
- ips_release(request, &reply);
- break;
-
- case IPS_REQUEST_OPS:
- ips_request_list(request, &reply);
-
- break;
-
- case IPS_REQUEST_LOADERS:
- ips_request_list(request, &reply);
- break;
-
- case IPS_REQUEST_SAVERS:
- ips_request_list(request, &reply);
- break;
-
- default:
- reply.op = ERROR;
- }
-
- return &reply;
- }
-
-
- void
- ips_release(request, reply)
- Packet *request, *reply;
- {
- char buf[1024];
-
- sprintf(buf, "rm -f %s/%d:*",
- STORAGE_DIR, request->Packet_u.id);
-
- system(buf);
-
- reply->op = IPS_RELEASE_OK;
- }
-
-
- void
- ips_hello(request, reply)
- Packet *request, *reply;
- {
- reply->Packet_u.hello_string = "Welcome to IPS.";
- reply->op = IPS_HELLO;
- }
-
- void
- ips_request_list(request, reply)
- Packet *request, *reply;
- {
- int i,
- found_listings = 0;
- char *temp_name;
- list *cp,
- *nlp = NULL;
- static DIR *dirp;
- struct direct *d;
-
-
- printf("IPS_REQUEST received.\n");
-
- switch(request->op) {
- case IPS_REQUEST_OPS:
- dirp = opendir(OPS_DIR);
-
- break;
-
- case IPS_REQUEST_LOADERS:
- dirp = opendir(LOADERS_DIR);
- break;
-
- case IPS_REQUEST_SAVERS:
- dirp = opendir(SAVERS_DIR);
- break;
- }
-
- nlp = &reply->Packet_u.operators;
-
- while(d = readdir(dirp)) {
- temp_name = d->d_name;
-
- if(strcmp(temp_name, ".") == 0
- strcmp(temp_name, "..") == 0)
- continue;
-
- found_listings = 1;
- nlp->name = strdup(temp_name);
- cp = nlp;
- nlp->next = (list *) malloc(sizeof(list));
- nlp = nlp->next;
- }
-
- closedir(dirp);
-
- if(found_listings)
- cp->next = nlp = NULL;
- else {
- nlp->name = "NONE_AVAILABLE";
- nlp->next = NULL;
- }
-
- switch(request->op) {
- case IPS_REQUEST_OPS:
- reply->op = IPS_SEND_OPS;
- break;
-
- case IPS_REQUEST_LOADERS:
- reply->op = IPS_SEND_LOADERS;
- break;
-
- case IPS_REQUEST_SAVERS:
- reply->op = IPS_SEND_SAVERS;
- break;
- }
- }
-
-
- void
- ips_process(request, reply)
- Packet *request, *reply;
- {
- int i, stat;
- long len;
- char buf[2000];
-
- char buf2[2020];
- unsigned char *data;
- char *filename = NULL;
-
-
- printf("IPS_PROCESS received.\n");
-
- sprintf(buf, "%s/%s", OPS_DIR,
- request->Packet_u.proc.command);
-
- filename =
- get_filename(request->Packet_u.send_ops.id);
-
- if(access(buf, R_OK) != 0
- strlen(filename) == 0) {
- reply->op = ERROR;
- return;
- }
-
- sprintf(buf, "%s/%s %s/%s %s/%s",
- OPS_DIR,
- request->Packet_u.proc.command,
- STORAGE_DIR,
- filename,
- STORAGE_DIR,
- filename);
-
- if(request->Packet_u.proc.argc > 0) {
- list *lp;
-
- lp = &request->Packet_u.proc.argv;
-
- for(i=0;i<request->Packet_u.proc.argc &&
- lp != NULL;i++, lp=lp->next) {
- strcat(buf, " ");
- strcat(buf, lp->name);
- }
- }
-
- if(system(buf) != 0)
- reply->op = ERROR;
- else
- reply->op = IPS_PROCESS_OK;
-
- return;
- }
-
-
- void
- ips_encode(request, reply)
- Packet *request, *reply;
- {
- int i, stat;
- long len;
- char buf[2000];
- char buf2[2020];
- unsigned char *data;
- char *filename = NULL;
-
-
-
- printf("IPS_REQUEST_IMAGE received.\n");
-
- filename = get_filename(request->Packet_u.send_ops.id);
-
- if(strlen(filename) == 0)
- reply->op = IPS_IMAGE_NOT_AVAIL;
- else {
- printf("\tSending %s image.\n",
- request->Packet_u.send_ops.format);
-
- sprintf(buf. "%s/%s", SAVERS_DIR,
- request->Packet_u.send_ops.format);
-
- if(access(buf, R_OK) != 0) {
- reply->op = IPS_UNSUPPORTED_FORMAT;
- return;
- }
-
- sprintf(buf, "%s/%s %s/%s %s/%s";
- SAVERS_DIR,
- request->Packet_u.send_ops.format,
- STORAGE_DIR,
- filename,
- STORAGE_DIR,
- replace_ext(filename,
- request->Packet_u.send_ops.format));
-
- if(request->Packet_u.send_ops.argc > 0) {
- list *lp;
-
- lp = &request->Packet_u.send_ops.argv;
-
- for(i=0;
- i<request->Packet_u.send_ops.argc
- && lp != NULL;
- i++, lp=lp->next) {
-
- strcat(buf, "");
- strcat(buf, lp->name);
- }
- }
-
- printf("\tConverting IPS to %s...\n",
- request->Packet_u.send_ops.format);
-
- stat = system(buf);
-
- if(stat != 0) {
- printf("\tError %d.\n", stat);
- reply->op = ERROR;
-
- sprintf(buf, "%s/%s",
- STORAGE_DIR, replace_ext(filename,
- request->Packet_u.send_ops.format));
-
- sprintf(buf2, "rm -f %s", buf);
- system(buf2);
-
-
- return;
- }
-
- printf("\tSuccess.\n");
-
- sprintf(buf, "%s/%s", STORAGE_DIR,
- replace_ext(filename,
- request->Packet_u,send_ops.format));
-
- data = loadfile(buf, &len);
-
- sprintf(buf2, "rm -f %s", buf);
- system(buf2);
-
- reply->Packet_u.img.data.data_len = len;
- reply->Packet_u.img.data.data_val =
- (char *) data;
- reply->op = IPS_SEND_IMAGE;
- }
- }
-
-
- void
- ips_decode(request, reply)
- Packet *request, *reply;
- {
- FILE *fp;
- char buf[256];
- char format[10];
- unsigned char *data;
- unsigned long id;
- int status;
- char new_filename[1024];
-
-
- printf("IPS_DECODE received.\n");
-
- id = get_next_id();
-
- reply->Packet_u.id = id;
-
- sprintf(buf, "%s/%ld:%s",
- STORAGE_DIR, id,
- request->Packet_u.raw_img.filename);
-
- if((fp = fopen(buf, "w")) == NULL) {
- printf("\tError in opening file %s.\n", buf);
- reply->op = ERROR;
- return;
- }
-
- fwrite(request->Packet_u.raw_img.data.data_val, 1,
- request->Packet_u.raw_img.data.data_len, fp);
- fclose(fp);
-
- strcpy(new_filename, buf);
- strcpy(format,
- upcase(request->Packet_u.raw_img.fomat));
-
-
- if(strcmp(format, "UNKNOWN") == 0) {
- printf("\tUnknown format.\n");
- reply->op = IPS_UNKNOWN_FORMAT;
- return;
- }
-
- sprintf(buf, "%s/%s", LOADERS_DIR, format);
-
- if(access(buf, R_OK) == 0) {
- sprintf(buf, "%s/%s %s %s",
- LOADERS_DIR, format, new_filename,
- replace_ext(new_filename, "ips"));
-
- printf("\tConverting: DATA...%s...", format);
- fflush(stdout);
-
- status = system(buf);
-
- if(status == 0) {
- printf("\n\tSuccess.\n");
- sprintf(buf, "rm %s", new_filename);
- system(buf);
- reply->op = IPS_OK_ID;
-
- } else {
- printf("\tUnsupported format.\n");
- reply->op = IPS_UNSUPPORTED_FORMAT;
- }
-
- } else {
- printf("\tUnsupported format.\n");
- reply->op = IPS_UNSUPPORTED_FORMAT;
- }
- }
-
-
- int
- get_next_id()
- {
- static unsigned long current_id = 0;
- current_id++;
-
- if(current_id > 999999)
- current_id = 0;
-
- return current_id;
- }
-
-
- char *
- replace_ext(filename, ext)
- char *filename;
- char *ext;
- {
- static char newbuf[1024];
- char temp[1024];
- char *p;
-
- strcpy(temp, filename);
-
- p = temp;
-
- while(*p != '.' && *p ! = 0)
- p++;
-
- if(*p != 0)
- *p = 0;
-
- trcpy(newbuf, temp);
- strcat(newbuf, ".");
- strcat(newbuf, ext);
-
- return newbuf;
- }
-
-
- char *
- get_filename(id)
- unsigned long id;
- {
- static DIR *dirp;
- struct direct *d;
- int temp_id;
- static char filename[1024];
- struct filelist_node {
- char *filename;
- struct filelist_node *next;
- };
-
- struct filelist_node filelist, *clp, *flp;
-
- dirp = opendir(STORAGE_DIR);
-
- flp = &filelist;
-
- while(d = readdir(dirp)) {
- flp->filename = d->d_name;
- flp->next = (struct filelist_node *)
- malloc(sizeof(struct filelist_node));
- clp = flp;
- flp = flp->next;
- }
-
- closedir(dirp);
-
- clp->next = NULL;
- filename[0] = NULL;
-
- for(flp=&filelist;flp != NULL; flp=flp->next) {
- int temp_id;
-
- if(strcmp(flp->filename, ".") == 0
- strcmp(flp->filename, "..") == 0)
- continue;
-
- sscanf(flp->filename, "%d:", &temp_id);
-
- if(temp_id == id)
- strcpy(filename, flp->filename);
-
- }
-
- for(flp=&filelist;clp != NULL; flp=clp=flp->next)
- free(flp);
-
- return filename;
- }
-
-
- unsigned char *
- loadfile(filename, len)
- char *filename;
- long *len;
- {
- FILE *fp;
- long size;
- unsigned char *raw_img;
-
- fp = fopen(filename, "r");
- fseek(fp, 0, 2);
- *len = size = ftell(fp);
-
- fseek(fp, 0, 0);
-
- raw_img = (unsigned char *) malloc(size);
- fread(raw_img, 1, size, fp);
- fclose(fp);
-
- return raw_img;
- }
-
-
- char *
- upcase(text)
- char *text;
- {
- char *p;
- static char buf[1000];
-
- strcpy(buf, text);
- p = buf;
-
- while(*p != NULL) {
- *p = toupper(*p);
- p++;
- }
-
- return buf;
- }
- /* End of File */
-
-
- Listing 3 The client program
- /*
- * Listing 3 - demo_client.c
- */
-
- #include <stdio.h>
- #include <rpc/rpc.h>
-
- #include <malloc.h>
- #include <string.h>
- #include <fcntl.h>
- #include <unistd.h>
- #include <ctype.h>
- #include <sys/time.h>
- #include "ips.h"
-
- /* Prototypes */
- char *get_file();
- void image_release();
- void upcase();
- void help_text();
-
- /* Globals */
- static unsigned long image_id=-1;
- static struct timeval Timeout = { 10000, 0 };
-
- void
- main(argc, argv)
- int argc;
- char **argv;
- {
- int done = 0;
- CLIENT *cl;
- char cmd[256];
- Packet *request, *reply;
- int id;
- char command[256];
-
- request = (Packet *) malloc(sizeof(Packet));
-
- if(argc < 2) {
- printf("Usage: %s server\n", argv[0]);
- exit(0);
- }
-
- if(!(cl = clnt_create(argv[1], IPSPROG, IPSVERS,
- "tcp"))) {
- clnt_pcreateerror(argv[1]);
- exit(1);
- }
-
- clnt_control(cl, CLSET_TIMEOUT, (char *)&Timeout);
-
- request->op = IPS_INIT;
- reply = ips_1(request, cl);
-
- if(reply)
- printf("\n%s\n\n",
- reply->Packet_u.hello_string);
-
- while(!done) {
- printf("(? for help)>>");
- fflush(stdout);
- gets(cmd);
-
- if(strcmp(cmd, "help") == 0
- strcmp(cmd, "?") == 0)
-
- help_text();
-
- else if(strcmp(cmd, "send") == 0)
- decode(cl);
-
- else if(strcmp(cmd, "get") == 0)
- encode(cl);
-
- else if(strcmp(cmd, "process") == 0)
- process(cl);
-
- else if(strcmp(cmd, "operators") == 0)
- get_operators(cl);
-
- else if(strcmp(cmd, "loaders") == 0)
- get_loaders(cl);
-
- else if(strcmp(cmd, "savers") == 0)
- get_savers(cl);
-
- else if(strcmp(cmd, "quit") == 0) {
- image_release(cl);
- done = 1;
- }
- printf("\n");
- }
- }
-
-
- int
- encode(cl)
- CLIENT *cl;
- {
- char buf[256];
- char filename[256];
- char format[40];
- Packet *reply, *request;
-
-
- request = (Packet *) malloc(sizeof(Packet));
-
- printf("\nIPS Send Image:\n\n");
-
- printf("Enter image filename (abort to quit): ");
- fflush(stdout);
- gets(filename);
- if(strcmp(filename, "abort") == 0)
- return 1;
-
- printf("Enter format (abort to quit): ");
- fflush(stdout);
- gets(format);
- if(strcmp(format, "abort") == 0)
- return 1;
-
- upcase(format);
-
- request->op = IPS_REQUEST_IMAGE;
- request->Packet_u.send_ops.id = image_id;
-
- request->Packet_u.send_ops.format = format;
- request->Packet_u.send_ops.argc = 0;
- request->Packet_u.send_ops._argv.name = "test";
- request->Packet_u.send_ops._argv.next = NULL;
-
- reply = ips_1(request, cl);
-
- if(!reply) {
- puts("Reply error or timeout.");
- exit(1);
- }
-
- if(reply->op == IPS_SEND_IMAGE) {
- FILE *fp;
-
- fp = fopen(filename, "w");
- fwrite(reply->Packet_u.img.data.data_val, 1,
- reply->Packet_u.img.data.data_len, fp);
- fclose(fp);
-
- printf("\"%s\" Received.\n", filename);
-
- } else if(reply->op == IPS_IMAGE_NOT_AVAIL)
- printf("IPS_IMAGE_NOT_AVAIL recevied.\n");
-
- else if(reply->op == IPS_UNSUPPORTED_FORMAT)
- printf("IPS_UNSUPPORTED_FORMAT received\n");
-
- else {
- printf("Error in encoding.\n");
- return 1;
- }
-
- free(request);
- if(reply) xdr_free(xdr_Packet, reply);
-
- return 0;
- }
-
-
- int
- decode(cl)
- CLIENT *cl;
- {
- FILE *fp;
- long size;
- char buf[256];
- char buf2[256];
- char *filename;
- unsigned char *raw_img;
- Packet *reply, *request;
-
-
- request = (Packet *) malloc(sizeof(Packet));
-
- if((filename = get_file()) == NULL) {
- printf("Error or aborted.\n");
- return 1;
- }
-
-
- printf("Please enter image format: ");
- fflush(stdout);
- scanf("%s", buf2);
-
- upcase(buf2);
-
- image_release(cl);
-
- sprintf(buf, "%s", filename);
-
- fp = fopen(buf, "r");
- fseek(fp, 0, 2);
- size = ftell(fp);
-
- fseek(fp, 0, 0);
-
- raw_img = (unsigned char *) malloc(size);
- fread(raw_img, 1, size, fp);
- fclose(fp);
-
- request->op = IPS_DECODE;
- request->Packet_u.raw_img.filename = filename;
- request->Packet_u.raw_img.format = strdup(buf2);
- request->Packet_u.raw_img.data.data_len = size;
- request->Packet_u.raw_img.data.data_val =
- (char *) raw_img;
-
- reply = ips_1(request, cl);
-
- if(!reply) {
- puts("Timeout.");
- exit(1);
- }
-
- if(reply->op == IPS_OK_ID) {
- image_id = reply->Packet_u.id;
- } else {
- printf("Error in decoding.\n\n");
- return 1;
- }
-
- free(raw_img);
- free(request);
- xdr_free(xdr_Packet, reply);
-
- return 0;
- }
-
-
- int
- process(cl)
- CLIENT *cl;
- {
- int i,
- retval;
- list *nlp, *cp;
- char buf[256];
- Packet *reply = NULL,
-
- *request;
- int nitems;
- char *items[10];
-
- if(image_id == -1) {
- printf("No image sent to server.\n");
- return;
- }
-
- /*
- For this article, nitems will be 0, but
- this allows you to send arguments to the
- operator program. For example, if you
- were executing a SCALE program, you would
- have to send the new width and height.
- */
-
- nitems = 0;
-
- request = (Packet *) malloc(sizeof(Packet));
-
- printf("Please enter processing command: ");
- fflush(stdout);
- scanf("%s", buf);
-
- upcase(buf);
-
- request->op = IPS_PROCESS;
- request->Packet_u.proc.id = image_id;
- request->Packet_u.proc.command = strdup(buf);
- request->Packet_u.proc.argc = nitems;
-
- if(nitems == 0) {
- request->Packet_u.proc.argv.name = "\0";
- request->Packet_u.proc.argv.next = 0;
-
- } else {
- nlp = &request->Packet_u.proc.argv;
- for(i=0;i<nitems;i++) {
- nlp->name = strdup(items[i]);
- cp = nlp;
- nlp->next = (list *) malloc(sizeof(list));
- nlp = nlp->next;
- }
- cp->next = nlp = NULL;
- }
-
- reply = ips_1(request, cl);
-
- free(request);
-
- if(!reply) {
- puts("Encode timeout.");
- return -1;
- }
-
- if(reply->op == IPS_PROCESS_OK)
- retval = 0;
- else {
-
- puts("There was an error processing.\n");
- retval = -1;
- }
-
- xdr_free(xdr_Packet, reply);
-
- return retval;
- }
-
-
- int
- get_loaders(cl)
- CLIENT *cl;
- {
- list *nlp;
- Packet *reply, *request;
-
-
- request = (Packet *) malloc(sizeof(Packet));
-
- request->op = IPS_REQUEST_LOADERS;
- reply = ips_1(request, cl);
-
- switch(reply->op) {
- case IPS_SEN_LOADERS:
- printf("\nIPS Load Formats:\n\n");
- for(nlp = &reply->Packet_u.operators;
- nlp != NULL;
- nlp = nlp->next)
- printf(" %s\n", nlp->name);
- printf("\n");
- break;
-
- case ERROR:
- printf("get_loaders failed.\n");
- return 1;
- }
-
- free(request);
- xdr_free(xdr_Packet, reply);
-
- return 0;
- }
-
- int
- get_savers(cl)
- CLIENT *cl;
- {
- list *nlp;
- Packet *reply, *request;
-
-
- request = (Packet *) malloc(sizeof(Packet));
-
- request->op = IPS_REQUEST_SAVERS;
- reply = ips_1(request, cl);
-
- switch(reply->op) {
- case IPS_SEND_SAVERS:
-
- printf("\nIPS Save Formats:\n\n");
- for(nlp = &reply->Packet_u.operators;
- nlp != NULL;
- nlp = nlp->next)
- printf(" %s\n", nlp->name);
- printf("\n");
- break;
-
- case ERROR:
- printft"get_savers failed.\n");
- return 1;
- }
-
- free(request);
- xdr_free(xdr_Packet, reply);
-
- return 0;
- }
-
-
- int
- get_operators(cl)
- CLIENT *cl;
- {
- list *nlp;
- Packet *reply, *request;
-
-
- request = (Packet *) malloc(sizeof(Packet));
-
- request->op = IPS_REQUEST_OPS;
- reply = ips_1(request, cl];
-
- switch(reply->op) {
- case IPS_SEND_OPS:
- printf("\nIPS Processing Tools:\n\n");
- for(nlp = &reply->Packet_u.operators;
- nlp != NULL;
- nlp = nlp->next)
- printf(" %s\n", nlp->name);
- printf("\n");
- break;
- case ERROR:
- printf("get_operators failed.\n");
- return 1;
- }
-
- free(request);
- xdr_free(xdr_Packet, reply);
-
- return 0;
- }
-
-
- char *
- get_file()
- {
- int status;
- char filename[1024], command[1024];
-
-
- printf("\nIPS Receive Image:\n\n");
- printf("Type 'abort' to cancel. Filename: ");
- fflush(stdout);
- gets(filename);
-
- if(strcmp(filename, "abort") == 0)
- return NULL;
-
- if(access(filename, R_OK) == 0)
- return strdup(filename);
-
- return NULL;
- }
-
-
- void
- image_release(cl)
- CLIENT *cl;
- {
- Packet *request, *reply;
-
- request = (Packet *) malloc(sizeof(Packet));
-
- if(image_id != -1) {
- request->op = IPS_IMAGE_RELEASE;
- request->Packet_u.id = image_id;
- reply = ips_1(request, cl);
-
- if(reply->op == IPS_RELEASE_OK)
- printf("Release of %d ok.\n", image_id);
- }
- free(request);
- }
-
-
- void
- upcase(text)
- char *text;
- {
- char *p = text;
-
- while(*p != NULL) {
- *p = toupper(*p);
- p++;
- }
- }
-
-
- void
- help_text()
- {
- printf("\n");
- printf(" Cmd Description\n");
- printf("------- -----------------------\n");
- printf(" send send an image to the IPS\n");
- printf(" get receive an image from IPS\n");
- printf(" in a certain format\n");
- printf(" process process an image\n");
-
- printf(" loaders list all available loaders\n");
- printf(" savers list all available savers\n");
- printf(" operators list all avail. operators\n");
- printf(" quit exit this client\n");
- printf("\n");
- }
-
- /* End of File */
-
-
- Listing 4 Internal image format
- /*
- * Listing 4 - ips_header.h
- */
-
- #ifndef _IPS_IMAGE_H
- #define _IPS_IMAGE_H
-
- #include <X11/Xlib.h>
-
- typedef struct {
- int cmap_count;
- int bpp;
- int width;
- int height;
- } ips_header;
-
- typedef struct {
- XColor cmap[256];
- unsigned char *data;
- } ips_image;
-
- #endif
- /* End of File */
-
-
- Listing 5 Small file I/O library for loading and saving images
- /*
- * Listing 5 - fileio.c (libips.a)
- */
-
- #include <stdio.h>
- #include "ips_image.h"
-
- int img_to_ips();
- int img_to_ips_fp();
- int ips_to_img();
- int ips_to_img_fp();
-
-
- int
- img_to_ips(header, image, filename)
- ips_header *header;
- ips_image *image;
- char *filename;
- {
- int retval;
- FILE *fp;
-
-
-
- if((fp = fopen(filename, "w")) == NULL) {
- perror("img_to_ips");
- return 0;
- }
-
- retval = img_to_ips_fp(header, image, fp);
-
- fclose(fp);
-
- return retval;
- }
-
- int
- img_to_ips_fp(header, image, fp)
- ips_header *header;
- ips_image *image;
- FILE *fp;
- {
- fwrite(header, sizeof(ips_header), 1, fp);
- fwrite(image->cmap, 1, sizeof(XColor) * header->cmap_count, fp);
- fwrite(image->data, 1, header->width * header->height, fp);
-
- return 1;
- }
-
-
- int
- ips_to_img(header, image, filename)
- ips_header *header;
- ips_image *image;
- char *filename;
- {
- int retval;
- FILE *fp;
-
-
- if((fp = fopen(filename, "r")) == NULL) {
- perror("ips_to_img");
- return 0;
- }
-
- retval = ips_to_img_fp(header, image, fp);
-
- fclose(fp);
-
- return retval;
- }
-
- int
- ips_to_img_fp(header, image, fp)
- ips_header *header;
- ips_image *image;
- FILE *fp;
- {
- fread(header, sizeof(ips_header), 1, fp);
-
- if(header->bpp != 8) {
- fprintf(stderr,
-
- "ips_to_img_fp: data is not in 8 bit format.\n");
- return 0;
- }
-
- image->data = (unsigned char *)
- malloc(header->width * header->height);
-
- if(image->data == NULL) {
- perror("ips_to_img_fp");
- return 0;
- }
-
- fread(image->cmap, 1, sizeof(XColor) * header->cmap_count, fp);
- fread(image->data, 1, header->width * header->height, fp);
-
- return 1;
- }
- /* End of File */
-
-
- Listing 6 X-Window Dump (XWD) image format loader
- /*
- * Listing 6 - xwd.c (loader)
- */
-
- #include <stdio.h>
- #include <math.h>
- #include <X11/XWDFile.h>
- #include "ips_image.h"
-
- void read_ximage();
-
- int
- main(argc, argv)
- int argc;
- char **argv;
- {
- ips_header header;
- ips_image image;
-
- if (argc < 3) {
- printf("Usage: %s infile outfile\n", argv[0]);
- exit(1);
- }
-
- read_ximage(&header, &image, argv[1]);
-
- img_to_ips(&header, &image, argv[2]);
-
- exit(0);
- }
-
-
- void
- read_ximage(header, image, filename)
- ips_header *header;
- ips_image *image;
- char *filename;
- {
-
- int i, pad, rightpad;
- char buf[1024];
- FILE *fp;
- unsigned char *p;
- XWDColor xcmap[256];
- XWDFileHeader xheader;
-
- if((fp = fopen(filename, "r")) == NULL) {
- perror("read_ximage");
- exit(1);
- }
-
- fread(&xheader, 1, sizeof(XWDFileHeader), fp);
- fread(buf, 1, (xheader.header_size - sizeof(XWDFileHeader)), fp);
- fread(xcmap, 1, sizeof(XWDColor) * xheader.ncolors, fp);
-
- p = image->data = (unsigned char *)
- malloc(xheader.pixmap_width * header.pixmap_height);
-
- rightpad = xheader.bytes_per_line - xheader.pixmap_width;
-
- for (i=0; i<xheader.pixmap_height; i++) {
- fread((p + (i * xheader.pixmap_width)), 1,
- xheader.pixmap_width, fp);
-
- for (pad=0; pad < rightpad; pad++)
- fgetc(fp);
- }
-
- header->bpp = 8;
- header->cmap_count = xheader.ncolors;
- header->width = xheader.pixmap_width;
- header->height = xheader.pixmap_height;
-
- for(i=0; i < header->cmap_count; i++) {
- image->cmap[i].pixel = i;
- image->cmap[i].red = xcmap[i].red >> 8;
- image->cmap[i].green = xcmap[i].green >> 8;
- image->cmap[i].blue = xcmap[i].blue >> 8;
- }
-
- fclose(fp);
- }
- /* End of File */
-
-
- Listing 7 A loader that auto-detects the image file format
- /*
- * Listing 7 - auto-detect.c
- */
-
- #include <stdio.h>
- #include <unistd.h>
-
- int
- main(argc, argv)
- int argc;
- char **argv;
- {
-
- FILE *fp;
- char buf[1000];
- char buf2[1000];
- char magic_number[12];
- char format[5];
-
- if(argc < 3) {
- printf("Usage: %s infile outfile\n", argv[0]);
- exit(1);
- }
-
- fp = fopen(argv[1], "r");
-
- fread(magic_number, sizeof(char),
- sizeof(magic_number), fp);
-
- fclose(fp);
-
- if (strncmp(magic_number,"GIF8",4) == 0)
- (void) strcpy(format,"GIF");
-
- else if ((magic_number[1] == 0x00) &&
- (magic_number[2] == 0x00)) {
- if ((magic_number[5] == 0x00) &&
- (magic_number[6] == 0x00))
- if ((magic_number[4] == 0x07)
- (magic_number[7] == 0x07))
- strcpy(format,"XWD");
-
- } else exit(1);
-
- if(strlen(format) == 0) exit(1);
-
- sprintf(buf2, "%s/%s",
- getenv("LOADERS_DIR"), format);
-
- if(access(buf2, R_OK) != 0) {
- printf("\n%s: %s format not available.\n",
- argv[0], format);
-
- exit(1);
- }
-
- sprintf(buf, "%s %s %s", buf2, argv[1], argv[2]);
-
- return system(buf);
- }
- /* End of File */
-
-
- Listing 8 Example XWD format saver
- /*
- * Listing 8 - xwd.c (saver)
- */
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <math.h>
- #include <X11/XWDFile.h>
-
- #include "ips_image.h"
-
- void write_ximage();
-
- int
- main(argc, argv)
- int argc;
- char **argv;
- {
- ips_header header;
- ips_image image;
-
- if (argc<3) {
- printf("Usage: %s infile outfile\n", argv[0]);
- exit(1);
- }
-
- ips_to_img(&header, &image, argv[1]);
-
- write_ximage(&header, &image, argv[2]);
-
- exit(0);
- }
-
-
- void
- write_ximage(header, image, filename)
- ips_header *header;
- ips_image *image;
- char *filename;
- {
- int i;
- FILE *fp;
- XWDFileHeader xheader;
- XWDColor xcmap[256];
-
-
- memset(&xheader, 0, sizeof(XWDFileHeader));
-
- xheader.header_size = sizeof(XWDFileHeader);
- xheader.file_version = XWD_FILE_VERSION;
- xheader.pixmap_format = 2; /* ZPixmap */
- xheader.pixmap_depth = header->bpp;
- xheader.pixmap_width = header->width;
- xheader.pixmap_height = header->height;
- xheader.byte_order = 1;
- xheader.bitmap_unit = 32;
- xheader.bitmap_bit_order = 1;
- xheader.bitmap_pad = 32;
- xheader.bits_per_pixel = 8;
- xheader.bytes_per_line = header->width;
- xheader.visual_class = 3; /* PseudoColor */
- xheader.bits_per_rgb = header->bpp;
- xheader.colormap_entries = header->cmap_count;
- xheader.ncolors = header->cmap_count;
- xheader.window_width = header->width;
- xheader.window_height = header->height;
-
- for (i=0; i < header->cmap_count; i++) {
-
- xcmap[i].pixel = i;
- xcmap[i].red = image->cmap[i].red << 8;
- xcmap[i].green = image->cmap[i].green << 8;
- xcmap[i].blue = image->cmap[i].blue << 8;
- }
-
- if ((fp = fopen(filename, "w")) == NULL) {
- perror("write_ximage");
- exit(1);
- }
-
- fwrite(&xheader, 1, sizeof(XWDFileHeader), fp);
- fwrite(xcmap, 1, sizeof(XWDColor) * header->cmap_count, fp);
- fwrite(image->data, 1, header->width * header->height, fp);
-
- fclose(fp);
- }
- /* End of File */
-
-
- Listing 9 Example image operator
- /*
- * Listing 9 - flipy.c
- */
-
- #include <stdio.h>
- #include "ips_image.h"
-
- void flipy();
-
- int
- main(argc, argv)
- int argc;
- char **argv;
- {
- ips_header header;
- ips_image image, newimage;
-
- if (argc<3) {
- printf("Usage: %s infile outfile", argv[0]);
- exit(1);
- }
-
- ips_to_img(&header, &image, argv[1]);
-
- flipy(&header, &image, &newimage);
-
- img_to_ips(&header, &newimage, argv[2]);
-
- exit(0);
- }
-
-
- void
- flipy(header, image, newimage)
- ips_header *header;
- ips_image *image, *newimage;
- {
- long cnt;
-
-
- memcpy(newimage->cmap, image->cmap, sizeof(XColor) * 256);
-
- newimage->data =
- (unsigned char *)malloc(header->width *
- header->height);
-
- for(cnt=0;cnt < header->width * header->height;cnt++)
- newimage->data[cnt] =
- image->data[(header->width * header->height) -
- cnt];
- }
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Literate Programming in C and C++ using CWEB
-
-
- Lee Wittenberg
-
-
- Lee Wittenberg teaches Computer Science at Kean College of New Jersey. He is
- an active contributor to the Literate Programming electronic discussion group,
- and can be reached via the Internet at leew@pilot.njin.net.
-
-
-
-
- Introduction
-
-
- A quiet revolution is taking place in programming circles -- a revolution
- called literate programming. In 1972, Edsger Dijkstra [1] wished for a
- "program written down as I can understand it, I want it written down as I
- would like to explain it to someone." Ten years later, Donald Knuth developed
- the original WEB system, coining the phrase "literate programming" in the
- process [2].
- Literate programming is precisely what Dijkstra wanted: the ability to write a
- program as you would explain it to another human being, rather than as your
- compiler would have it. In the decade since Knuth introduced the idea, the
- number of literate programming tools has proliferated. In addition to the
- original WEB, FunnelWeb, FWEB, noweb, Nuweb, Spidery WEB, and WinWordWEB are
- all available and in active use. But the flagship of the WEB fleet is CWEB, a
- preprocessor that supports literate programming in both C and C++, and (with
- the recent publication of "The Stanford GraphBase" [4] and "The CWEB System of
- Structured Documentation" [5]) is slowly moving into the mainstream, both in
- industry and academia.
-
-
- CWEB Program Format
-
-
- A CWEB program, commonly called a web, is made up of sections, each of which
- contains a text part and a code part, or chunk. The chunks can be extracted to
- create a program for compilers, or the entire web can be typeset to be read by
- people. A single source file serves the divergent interests of human and
- machine.
- Listing 1 is an example of a simple web. It shows how the traditional "hello
- world" program might look if written in CWEB. The sections are numbered, the
- code parts are cross-referenced by section number, and the identifiers and
- chunk names are indexed. Following Algol conventions, keywords are typeset in
- boldface, identifiers in italics, and strings in a typewriter font for easier
- reading.
- More important to note is the program layout. Instead of putting the line
- #include <stdio.h> at the beginning of the program, I use < Header files
- needed by the program 3 > as a placeholder. At this stage of the game, I know
- I'll be using header files, but -- in the best tradition of structured
- programming -- I'm not yet ready to make a decision about which ones I will
- need, so I leave the details up in the air until I know more. I can do the
- same thing with the body of main. In other words, I organize the program for
- the human reader, not the compiler.
- Each section of the web is a self-contained, self-explanatory piece of the
- program. Listing 1 amounts to overkill for such a tiny, well-known program,
- but Literate Programming is invaluable in dealing with larger programs, as
- Listing 4 will show.
- Listing 2 is the source file as I actually typed it, which CWEB used to
- generate Listing 1. This source file is basically a C program with embedded
- "markup" strings to delineate the literate programming features. Sections are
- introduced by @* or @ plus space followed by the text part. The code part of a
- section begins with @c, or with a chunk name followed by = . Chunk names are
- enclosed in @< and @>. Vertical bars surround bits of code embedded in text.
-
-
- Weaving and Tangling
-
-
- Generating a human-readable document from a marked-up source file is called
- "weaving the web." The CWEAVE command generates the section numbers,
- cross-references, and index. It also typesets the text and code properly, with
- occasional hints from the programmer (the control codes @; and @# are examples
- of such hints).
- The analogous procedure for generating a compiler-readable file is called
- tangling. Listing 3 shows the result of running the web in Listing 2 through
- CTANGLE. CTANGLE expands the @c chunk, recursively replacing chunk names with
- their definitions. If you look carefully, you can spot the classic program
- peeking through. CTANGLE inserts #line directives in the output file so the
- compiler and debugger can refer to the original web rather than the generated
- C file. Humans rarely need to look at tangled output, but for the sake of
- those who do, CTANGLE generates comments that link the tangled code to the
- numbered sections in the woven document.
-
-
- Creating a CWEB Program
-
-
- Since literate programs are meant to be read, I'm going to ask you to read
- through the program fragment in Listing 4. Before you do, however, I should
- warn you that CWEB provides a few notational conveniences that traditional C
- programmers often find disconcerting at first. CWEB replaces the symbols
- listed in Table 1 by mathematical equivalents. This substitution serves a dual
- purpose; it improves the readability of some of the more obscure operators,
- and provides a somewhat language-independent "publication language" for C.
- (WEB uses the same symbols for typesetting Pascal, and FWEB and Spidery WEB do
- likewise for the languages they support.) You can customize CWEB so it will
- typeset these symbols any way you like when you weave a web. However, you may
- find, as I do, that you prefer the "standard" CWEB symbols.
- Listing 4 demonstrates many of the advantages of literate programming. You can
- read the web straight through, or use the cross-referencing to follow a single
- thread. In addition to identifiers, the index contains references to
- "portability problems," "possible improvements," and other potentially useful
- items. "System dependencies" is a common index entry in webs. Reference lists,
- acknowledgements, and other useful bits of information not normally found in
- program documentation are common in literate programs.
- I wrote the program pretty much straight through, starting with section 1
- through section 20. [Sections 10 through 20 have been omitted here to save
- space. The complete listings are available on this month's code disk, as well
- as the original webs (.w files) and PostScript versions of the woven webs --
- mb.] I added sections 21-24 as they became necessary, and CWEAVE generated
- section 25 automatically. I proceeded topdown, starting with the main program
- and using chunk names to implement a process of stepwise refinement, keeping
- each section small and easy to understand. Since chunk names are
- typographically distinct from the actual code, they are much easier to read
- than function or macro names.
- For each code chunk, I thought about what it should do and how it should work,
- writing down these thoughts as the text part of a section. I then wrote the
- code part to meet these specifications. When you write a literate program, you
- usually write the code to match the documentation, rather than the other way
- around.
- I also like to include explanations of why I did (or didn't) do things in a
- particular way. Observations that would be intrusive in conventional program
- comments fit quite well in a literate program. I can use all of the techniques
- of word processing -- table of contents, footnotes, charts, graphs, etc. -- to
- better explain my code.
- Some of the benefits of literate programming as applied to Listing 4 do not
- show up in the final result. For example, I worked on this program over
- several months, leaving it alone for weeks at a time, when more pressing
- business called. I only had to read through the program once in order to pick
- up where I left off. It would have taken a full day, at least, with a
- non-literate program.
-
-
- C++ and CWEB
-
-
- Listing 5 is an example of a web that implements a C++ class. Since C++ syntax
- is more complex than that for C, you generally have to give CWEB more hints to
- get the prettyprinting correct, but the process is the same.
- CWEB allows a programmer to develop a class interface and implementation in
- lock-step. For example, I define the Xstring constructor and destructor
- interfaces in section 5, and follow immediately with their implementation in
- sections 6-11 [sections 8 through 17 not shown -- mb]. CTANGLE automatically
- extracts the class declaration in the <xstring.h> chunk to create the
- xstring.h file (because the chunk is named with @( instead of @<), so I don't
- have to worry about keeping information consistent across two separate files.
-
-
- Producing Formatted Output
-
-
- The CWEAVE program does not produce its formatted output directly. It produces
- a file that is then processed by the TeX (pronounced "Tekh") typesetting
- system. TeX is the typesetting system of choice for literate programming
- systems, for a variety of reasons:
-
- 1. TeX is readily available. Implementations exist for pretty much every
- computer in existence, and are usually available free of charge. I use the
- excellent emTeX implementation for MS-DOS, by Eberhard Mattes, which is freely
- distributable.
- 2. TeX is portable. It is designed to be as machine-independent as possible.
- Output generated by emTeX on my PC and that generated by, say, a UNIX
- implementation will be identical.
- 3. TeX is stable. Before a program can be certified as TeX, it must pass a
- rigorous test suite called the "trip test." Since TeX is not a commercial
- product, it is not subject to the whims of a manufacturer.
- 4. The quality of TeX output is significantly better than that of any
- commercial word processor or desktop publisher on the market today.
- On the other hand, there is no reason a literate programming system has to use
- TeX. A number of non-TeX systems exist, and many are suitable for programming
- in C and C++.
-
-
- Availability
-
-
- Although it is not in the public domain, CWEB is freely distributable. The
- official distribution is via anonymous ftp from labrea.stanford.edu on the
- Internet. The distribution includes source for UNIX, MS-DOS, VMS, and Amiga
- distribution. This version is also available on this month's code disk and via
- the online sources listed on page 3.
- TeX is available via ftp from the following sites:
-
-
- Host
-
-
- ftp.tex.ac.uk
- ftp.dante.de
- ftp.shsu.edu
- After logging in, cd to directory tex-archive/systems, then cd to appropriate
- subdirectory (msdos, unix, mac, etc.) for machine-specific TeX materials.
- Complete TeX systems for Amiga, Atari, Macintosh, MS-DOS, OS/2, UNIX, VM/CMS,
- VMS, and Windows NT are available on the Prime Time TeXcetera 1-1 collection
- on CD-ROM, available from R&D Publications for $60.00 plus $3.50 shipping and
- handling. Contact:
- R&D Publications
- 1601 W. 23rd St.
- Lawrence,KS 66046
- (913)-841-1631
- michelle@rdpub.com
- If you would like to find out more about literate programming, I recommend you
- subscribe to the LitProg discussion group on the Internet. All discussion is
- via electronic mail, so it should be possible to subscribe from CompuServe, or
- any other service that can send and receive Internet mail. To subscribe, send
- a message to LISTSERV@shsu.edu, and include
- SUBSCRIBE LitProg "your name"
- in the body of the message. If you prefer, you can subscribe to the
- comp.programming.literate newsgroup. All items posted to the newsgroup are
- automatically distributed to LitProg subscribers, and vice versa.
-
-
- Conclusion
-
-
- Literate programming is not for the beginner. The literate programmer must be
- proficient in at least two languages: a programming language, like C or C++,
- and a text formatting language, like TeX or a commercial word processor. Since
- the bulk of a web is explanation, rather than code, the programmer has to take
- the time to think through the program in order to be able to explain it. As in
- any programming methodology, time spent in thought "up front" translates into
- reduced debugging and easier maintenance. What literate programming adds to
- the mix is that the programmer's thoughts no longer disappear into thin air
- once the program is written; they are preserved in a web. The programmer who
- maintains the web has these thoughts as a foundation for future work. For the
- professional programmer, maintenance is everything. A literate program is a
- maintainable program.
- But, above all, literate programming is fun. As you write your explanations,
- the code unfolds naturally, as if it had always been there and you just now
- happened to find it. The phenomenon of "code that seems to write itself" is
- common among literate programmers, as is the practice of passing programs
- around for comments. The tedium of rearranging bits of code to cater to a
- compiler is gone. In its place is a process of discovery.
-
-
- Acknowledgements
-
-
- I'd like to thank Danielle Bernstein, Leonard Ginsburg, and Jack Ryder, whose
- comments and criticism greatly improved this article.
- References
- [1] Edsger W. Dijkstra. Structured Programming (Academic Press, 1972), "Notes
- on structured programming," pp. 1-82.
- [2] Donald E. Knuth. "Literate programming," The Computer Journal, May: 1984,
- pp. 97-111. Reprinted in Knuth's Literate Programming (Stanford University
- Center for the Study of Language and Information, 1992), pp. 99-135.
- [3] Donald E. Knuth. Computers and Typesetting (Addison-Wesley, 1986), Volume
- A--"The TeXbook."
- [4] Donald E. Knuth. The Stanford Graph-Base: A Platform for Combinatorial
- Computing. A collection of CWEB programs for generating and examining a wide
- variety of graphs and networks. (ACM Press, 1994).
- [5] Donald E. Knuth and Silvio Levy. The CWEB System of Structured
- Documentation (Addison-Wesley, 1994). ISBN 0-201-57569-8.
-
-
- Additional Information
-
-
- Walsh, Norman. Making Tex Work. O'Reilly & Associates, 1994. ISBN
- 1-56592-051-1.
- LitProg FAQ. FTP from the TeX archive sites listed in main article, directory
- help/LitProg- FAQ.
- Table 1 Symbols typeset by CWEB
- Standard C CWEB
- -----------------
- = ¬
-
- == º
- !=
- <= £
- >= >=
- &&
-
- ! ¬
- ^ Ã…
- NULL L
-
- Listing 1 A web for "hello, world"
- -------------------------------------------------------------
- A Simple Example ........................................ 1
- Index ................................................... 4
- -------------------------------------------------------------
- 1. A Simple Example. This is a trivial example of a CWEB
- program. It is, of course, the classic "hello, world" program we all
- know and love:
- á Header files needed by the program 3 ñ
- main (void)
- {
- á Print the message "hello, world" 2 ñ
- }
-
- 2. Naturally, we use printf to do the dirty work:
- á Print the message "hello, world" 2 ñ º
- printf ("hello, world\n");
- This code is used in section 1.
-
- 3. The prototype for printf is in the standard header, <stdio.h>.
- á Header files needed by the program 3 ñ º
- #include <stdio.h>
- This code is used in section 1.
-
- -------------------------------------------------------------
- 4. Index.
- main: 1. printf: 2, 3.
- -------------------------------------------------------------
- á Header files needed by the program 3 ñ Used in section 1.
- á Print the message "hello, world" 2 ñ Used in section 1.
-
-
- Listing 2 The text file that produced Listing 1
- \def\title{Listing 1}
-
- @*A Simple Example.
- This is a trivial example of a \.{CWEB} program.
- It is, of course, the classic "hello, world"
- program we all know and love:
-
- @c
- @<Header files needed by the program@>@;
- @#
- main(void)
- {
- @<Print the message "hello, world"@>@;
- }
-
- @ Naturally, we use printf to do the dirty work:
-
-
- @<Print the message "hello, world"@>=
- printf("hello, world\n");
-
- @ The prototype for printf is in the standard
- header, \.{<stdio.h>}.
-
- @<Header files needed by the program@>=
- #include <stdio.h>
-
- @*Index.
-
-
- Listing 3 Compiler-readable result of "tangling" Listing 2
- /*1:*/
- #line 8 "hello.w"
-
- /*3:*/
- #line 24 "hello.w"
-
- #include <stdio.h>
-
- /*:3*/
- #line 9 "hello.w"
-
- main(void)
- {
- /*2:*/
- #line 18 "hello.w"
-
- printf("hello, world\n");
-
- 1*:2*/
- #line 13 "hello.w"
-
- }
-
- /*:1*/
- /* End of File */
-
-
- Listing 4 A more elaborate example of Literate Programming
- -------------------------------------------------------------
- The Problem.............................................. 1
- The Solution............................................. 2
- Processing input lines............................... 6
- Command line options................................ 17
- Error messages...................................... 21
- References.............................................. 24
- Index................................................... 25
- -------------------------------------------------------------
- Copyright (C) 1994 by Lee Wittenberg.
- -------------------------------------------------------------
- 1. The Problem. Rather than make up a problem, we'll
- attempt to solve an exercise from The C Programming Language
- [1] using CWEB rather than plain C. In particular, we've chosen
- the following problem from page 34 of the second edition:*
-
- Exercise 1-22. Write a program to "fold" long
-
- input lines into two or more shorter lines after the
- last non-blank character that occurs before the
- n-th column of input. Make sure your program
- does something intelligent with very long lines,
- and if there are no blank or tabs before the
- specified column.
-
- This seems to imply that n = 80 requires output lines to contain
- at most 79 columns. It seems a bit more logical to let the user
- specify the maximum number of columns in the output, so that
- n = 80 will give us output lines of £ 80 characters. Since the
- problems are isomorphic (a solution to either is an "off by
- one" error for the other), we choose to solve the latter.
- -------------------------------------------------------------
- * A similar problem appears on page 31 of the first edition.
- -------------------------------------------------------------
- 2. The Solution. The structure of our program is fairly
- standard, and doesn't need a lot of explanation. After processing
- any command line options, we copy input lines to the standard
- output, folding them when necessary.
- Since we process all the options before we process any
- files, different options cannot be used for separate files.
- á Header files 3 ñ
- á Global variables 5 ñ
- á Functions 7 ñ;
- main (int argc, char *argv[])
- {
- á Scan command line options 19 ñ;
- á Allocate space for the input buffer 4 ñ;
- á Copy the input to the standard output,
- folding lines as necessary 6 ñ;
- return EXIT_SUCCESS;
- }
-
- 3. á Header files 3 ñº
- #include <stdlib.h>
- /* for EXIT_SUCCESS */
- See also sections 8 and 14.
- This code is used in section 2.
-
- 4. Since we don't want to place any unnecessary restrictions on
- line length, or on allowable values of n, we allocate space for
- the input buffer dynamically. We grab one character more
- than we need for a line, just in case a line contains exactly
- fold_column characters or we have a space in exactly the perfect
- spot (plus a byte for the '\0', of course).
- Since this is the only memory allocation we do, the malloc
- shouldn't fail, but you never can tell.
- á Allocate space for the input buffer 4 ñ º
- buffer ¬ (char *) malloc (fold_column + 2);
- if (buffer º L) {
- á Announce that we ran out of heap space 22 ñ;
- exit(EXIT_FAILURE);
- }
- This code is used in section 2.
-
- 5. Unless the user specifies otherwise, we assume that folding
- will occur after the 80th column.
- #define DEFAULT_FOLD 80U
-
- á Global variables 5 ñ º
- char *buffer;
- size_t fold_column ¬ DEFAULT_FOLD;
- See also section 18.
- This code is used in section 2.
-
- 6. Processing input lines. We assume that once we're ready to
- deal with input lines, the contents of argv have been
- "normalized" -- that all arguments that do not represent filenames have been
- replaced with null pointers. This will make our job a bit easier.
- We also assume that the string "-" used as a filename refers
- to the standard input.
- á Copy the input to the standard output, folding lines as
- necessary 6 ñ º
- if (á No file names were specified 17 ñ)
- fold_file ("-")
- else {
- int i;
- for (i ¬ 1; i < argc; i++) {
- if (argv[i] L)
- fold_file (argv[i]);
- }
- }
- This code is used in section 2.
-
- 7. The actual line-folding is done by the function fold_file,
- which takes the name of a file to be folded as its only argument.
- The filename "-" is taken to mean "use the standard input."
- á Functions 7 ñ º
- void fold_file (const char *filename)
- {
- FILE *infile;
- á Local variables for fold_file 10 ñ;
- if (strcmp (filename, "-" º 0)
- infile ¬ stdin;
- else {
- infile ¬ fopen (filename, "r" );
- if (infile º L) {
- á Warn the user that we couldn't open
- filename 21 ñ;
- return;
- }
- }
- á Copy infile to stdout, folding lines as necessary 9 ñ;
- if (infile stdin)
- fclose (infile);
- }
- This code is used in section 2.
-
- 8. < Header files 3 > +º
- #include <stdio.h>
- #include <string.h>
-
- 9. Whenever we fold an input line, we leave the portion after the
- fold in buffer. We use left_overs to let us know how much of
- buffer has already been used, and consequently, how much space
- is available for reading the rest of the line. The extra 2 characters
- specified in "fold_column - left_overs + 2" are for the '\n' and '\0'.
- á Copy infile to stdout, folding lines as necessary 9 ñ º
- while (fgets (buffer + left_overs,
- fold_column - left_overs + 2, infile)L)
-
- {
- á Fold input line in buffer, if necessary 11 ñ;
- }
- if (left_overs 0) /* incomplete last line */
- fprintf (stdout, "%.*s", (int) left_overs, buffer);
- This code is used in section 7.
- -------------------------------------------------------------
- [sections 10 through 20 omitted -- mb]
- -------------------------------------------------------------
- 21. Error messages. In a literate program, it is often helpful to
- the reader if all of the error handling code is described in the
- same place. This also helps the programmer make sure that the
- style of the messages is consistent throughout the program.
- á Warn the user that we couldn't open filename 21 ñ º
- fprintf(stderr,
- "I couldn't open the file \"%s\"\n.",filename);
- This code is used in section 7.
-
- 22. á Announce that we ran out of heap space 22 ñ º
- fprintf (stderr,
- "I couldn't allocate needed memory. Sorry.\n");
- This code is used in section 4.
-
- 23. á Tell user about unknown option in argv[i] 23 ñ º
- fprintf(stderr, "I don't know the '%s'
- option; I'll ignore it. \n", argv[i]);
-
- This code is used in section 20.
-
- 24. References.
- [1] Brian W. Kernighan and Dennis M. Ritchie. The C
- Programming Language. Prentice-Hall, second edition, 1988.
-
- 25. Index
- "-" as a filename: 6,7, 19. infile: 7, 9.
- argc: 2, 6, 17, 19. is_option: 19.
- argv: 2, 6, 18, 19, 20, 23. isdigit: 20.
- argv normalization: 6, 19. isspace: 13, 15, 16.
- buffer: 4, 5, 9, 11, 13, 15, 16. left_overs: 9, 10, 11, 16.
- DEFAULT_FOLD: 5. main: 2.
- exit: 4. malloc: 4.
- EXIT_FAILURE: 4. opt_count: 17, 18, 19.
- EXIT_SUCCESS: 2, 3. paranoid error checks: 4.
- fclose: 7. portability problems: 15.
- fgets: 9. possible improvements: 16, 20.
- filename: 7, 21. ptr: 12, 13, 15, 16.
- fold_column: 4, 5, 9, 11, 13, stderr: 21, 22, 23.
- 15, 16, 20. stdin: 7.
- fold_file: 6, 7. stdout: 9, 11, 16.
- fopen: 7. strcmp: 7.
- fprintf: 9, 16, 21, 22, 23. strcpy: 16.
- fputs: 11 strlen: 11, 16.
- i: 6, 19. strtoul: 20.
- incomplete specifications: 15, 16.
- -------------------------------------------------------------
- á Allocate space for the input buffer 4 ñ Used in section 2.
- á Announce that we ran out of heap space 22 ñ Used in section 4.
- á Copy the input to the standard output, folding lines
- as necessary 6 ñ Used in section 2.
-
- á Copy infile to stdout, folding lines as necessary 9 ñ
- Used in section 7.
- á Deal with specification flaw 15 ñ Used in section 13.
- á Determine an appropriate folding point, and fold the line 12 ñ
- Used in section 11.
- á Fold input line in buffer, if necessary 11 ñ Used in section 9.
- á Fold buffer at the place specified by ptr 16 ñ Used in section 12.
- á Functions 7 ñ Used in section 2.
- á Global variables 5, 18 ñ Used in section 2.
- á Header files 3, 8, 14 ñ Used in section 2.
- á Local variables for fold_file 10 ñ Used in section 7.
- á No file names were specified 17 ñ Used in section 6.
- á Point ptr at the appropriate place in buffer for the fold 13 ñ
- Used in section 12.
- á Process the option in argv[i] 20 ñ Used in section 19.
- á Scan command line options 19 ñ Used in section 2.
- á Tell user about unknown option in argv[i] 23 ñ Used in section 20.
- á Warn the user that we couldn't open filename 21 ñ Used in section 7.
-
-
- Listing 5 Using CWeb with C++
- -------------------------------------------------------------
- A C++ String Class...................................... 1
- Representing an Xstring................................ 4
- Construction and Destruction........................... 5
- Assignment............................................. 12
- Miscellaneous Operations............................... 14
- References.............................................. 18
- Index................................................... 19
- -------------------------------------------------------------
- Copyright (C) 1994 by Lee Wittenberg.
- Portions copyright (C) 1991 by AT&T Bell Telephone
- Laboratories, Inc.
- -------------------------------------------------------------
- 1. A C++ String Class. To demonstrate the use of CWEB for C++
- programming, we adapt the string class described by Stroustrup
- [1, pages 248-251]. Explanations in slanted type (including
- inline comments, when possible) are direct quotes from the
- original. We make a few minor changes along the way, but on the
- whole, we stick to Stroustrup's design.
-
- 2. We put the interface part of our class in the header file
- xstring.h. We call our class "Xstring" rather than "string" to
- avoid confusion with the original and other (more useful) string
- classes. We restrict ourselves to a lowercase file name to
- maintain portability among operating systems with case-insensitive
- file names.
- á xstring.h 2 ñ º
- #ifndef XSTRING_H
- #define XSTRING_H
- //prevent multiple inclusions
- class Xstring {
- á Private Xstring members 4 ñ
- public:
- á Public Xstring members 5 ñ
- };
- #endif
- This code is cited in section 3.
- This code is used in section 3.
-
-
- 3. We implement the class members in a single "unnamed
- chunk" that will be tangled to xstring.c (or xstring.cc or
- xstring.cpp, depending on your compiler's preference). We
- include the contents of < xstring. h 2 ) directly, rather than relying
- on #include, because we can.
- á Header files 8 ñ
- á xstring.h 2ñ
- á Xstring members and friends 6 ñ
-
- -------------------------------------------------------------
- 4. Representing an Xstring. The internal representation of an
- Xstring is simple. It counts the references to a string to
- minimize copying and uses standard C++ character strings as
- constants.
- á Private Xstring members 4 ñ º
- struct srep {
- char *s; // pointer to data
- int n;// reference count
- srep() { n ¬ 1; }
- };
- srep *p;
- See also section 16.
- This code is used in section 2.
-
- -------------------------------------------------------------
- 5. Construction and Destruction. The constructors and the
- destructor are trivial. We use the null string as a default
- constructor argument rather than a null pointer to protect against possible
- string. h function anomalies.
- á Public Xstring members 5 ñ º
- Xstring(const char *s ¬ "");
- // Xstring x ¬ "abc"
- Xstring(const Xstring &);
- // Xstring x ¬ Xstring ...
- ~Xstring();
- See also sections 12, 14, and 15.
- This code is used in section 2.
-
- 6. An Xstring constructed from a standard string needs space to
- hold the characters:
- á Xstring members and friends 6 ñ º
- Xstring::Xstring(const char *s)
- {
- p ¬ new srep;
- á Allocate space for the string and put a copy of s there 7 ñ;
- }
- See also sections 9, 10, 13, and 17.
- This code is used in section 3.
-
- 7. There is always the possibility that a client will try something
- like "Xstring x ¬ L." We substitute the null string whenever we are given a
- null pointer.
- á Allocate space for the string and put a copy of s there 7 ñ º
- if (s º L) s ¬ "";
- p(R) s ¬ new char [strlen (s) + 1 ];
- strcpy (p(R) s, s);
- This code is used in sections 6 and 13.
- -------------------------------------------------------------
- [Sections 8 through 17 omitted -- mb]
- -------------------------------------------------------------
-
- 18. References.
- [1] Bjarne Stroustrup. The C++ Programming Language.
- Addison-Wesley, second edition, 1991.
-
- -------------------------------------------------------------
- 19. Index.
- dummy: 15, 16, 17. strcat: 14.
- i: 15. strcpy: 7, 8.
- n: 4. strlen: 7, 14, 15.
- operator: 12, 13, 14, 15. x: 5, 7, 9, 13.
- p: 4. Xstring: 2, 6, 9, 10, 13.
- s: 4, 5, 6, 13. XSTRING_H: 2.
- srep: _4, 6, 13.
- -------------------------------------------------------------
- á Allocate space for the string and put a copy of s there 7 ñ
- Used in sections 6 and 13.
- á Decrement reference count, and remove p if necessary 11 ñ
- Used in sections 10 and 13.
- á Header files 8 ñ Used in section 3.
- á Private Xstring members 4, 16 ñ Used in section 2.
- á Public Xstring members 5, 12, 14, 15 ñ Used in section 2.
- á xstring.h 2 ñ Cited in section 3. Used in section 3.
- á Xstring members and friends 6, 9, 10, 13, 17 ñ Used in section 3.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Understanding the C Standard
-
-
- Clive D.W. Feather
-
-
- This article is not available in electronic form.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Standard C/C++
-
-
- Implementing <fstream>
-
-
-
-
- P.J. Plauger
-
-
- P.J. Plauger is senior editor of C/C++ Users Journal. He is convener of the
- ISO C standards committee, WG14, and active on the C++ committee, WG21. His
- latest books are The Draft Standard C++ Library, and Programming on Purpose
- (three volumes), all published by Prentice-Hall. You can reach him at
- pjp@plauger.com.
-
-
-
-
- Introduction
-
-
- Last month, I introduced the header <fstream> from the draft Standard C++
- library. (See "Standard C: The Header <fstream>," CUJ, April 1995.) It
- provides the classes you need to read and write external files and devices, in
- the guise of extracting from an istream object or inserting to an ostream
- object. I continue this month with a description of one way to implement these
- classes.
- This implementation should be largely familiar to those of you with some
- experience with existing iostreams packages. While the draft C++ Standard has
- introduced various small changes from traditional technology, char-based file
- I/O is heavily rooted in existing practice. The really new stuff is all the
- templates introduced into the library. I plan to avoid talking about them for
- a bit longer, at least.
- Listing 1 shows the file fstream, which implements the standard header
- <fstream>. It defines the classes filebuf, ifstream, ofstream, stdiobuf,
- istdiostream, and ostdiostream. I begin with several notes on class filebuf,
- which is the workhorse class for this header.
- The incomplete type declaration struct _Filet is an alias for the type FILE,
- declared in <stdio.h>. The header <fstream> is not permitted to include
- <stdio.h>, but it needs to declare parameters and member function return
- values compatible with type FILE. A secret synonym solves the problem. My
- implementation of the Standard C library [1] introduces _Filet for a similar
- reason. For another implementation, you may have to alter the name, or
- introduce extra machinery, to achieve the same effect.
- (A recent addition to C++ is the ability to gather multiple declarations into
- separate namespaces. The entire Standard C++ library then inhabits the
- namespace std. With such protection, an implementation can more safely include
- C headers in C++ headers -- though a few problems with macro names have yet to
- be widely discussed. Until namespace support becomes widespread, however, the
- sort of implementation presented here is prudent.)
- I added a constructor with the signature filebuf(_Filet *). (I did so by
- adding a default argument to the default constructor.) This implementation
- performs all file operations mediated by class filebuf through an associated
- FILE object. The member object _File points at the associated object, or
- stores a null pointer if none exists. The secret member function _Init,
- described below, initializes a filebuf object. Its arguments specify the
- initial values stored in the _File member objects.
- The type _Uninitialized, and the value _Noinit, mark the constructor used to
- perform some "double constructor" magic. The standard streams make use of this
- magic so they can be usable even within static constructors and destructors
- for other objects. (See "Standard C: The Header <ios>," CUJ, May 1994.)
- Note that class stdiobuf is based on class filebuf. The draft C++ Standard
- says that stdiobuf is derived directly from streambuf. I pulled a similar
- trick in implementing the header <sstream>, deriving stringbuf from
- strstreambuf. (See "Standard C: The Header <sstream>," CUJ, March 1995.) As
- before, such indirect derivation is permitted by the library "front matter."
- A fundamental difference exists between the classes filebuf and stdiobuf,
- however. Destroying a filebuf object closes any associated file. Destroying a
- stdiobuf object does not. I added the member object _Closef to filebuf to tell
- its destructor what to do. The stored value is nonzero only if the file is to
- be closed when the object is destroyed. That only happens after filebuf::open
- successfully opens a file.
-
-
- Improving Performance
-
-
- Listing 2 shows the file filebuf.c, which defines a number of functions
- required for practically any use of class filebuf. On the face of it, this
- implementation errs strongly on the side of portability, at the cost of
- performance. All of the member functions defined here will work atop any
- Standard C library. Moreover, none of the functions buffer reads or writes,
- beyond whatever buffering that may occur in the associated FILE object.
- Before you dismiss this as a purely tutorial implementation of class filebuf,
- take a closer look at the last function definition in the file. As I mentioned
- above, the member function _Init initializes all filebuf objects when they are
- constructed. It also reinitializes an object after a successful call to
- filebuf::open. And it can be made to do one very important additional thing.
- As I indicated way back in June 1994, I defined the base class streambuf from
- the outset with a bit of extra flexibility. The six pointers that control
- in-memory buffers are all indirect pointers. You can point them at pointers
- within the streambuf object itself, or at pointers in another object. For the
- derived class filebuf, you can sometimes choose the latter course to
- advantage. That's why the macro_HAS_PJP_CLIB chooses between two different
- calls to streambuf::_Init, which initializes all those direct and indirect
- pointers in the base subobject.
- An arbitrary Standard C library should contain no definition for this macro.
- The code works correctly, if not as fast as many would like. But for operation
- atop my implementation of the Standard C library [1], you can do much better.
- Define the macro _HAS_PJP_CLIB and the indirect pointers are set differently.
- They point at the pointers stored in the FILE object controlling access to the
- file. The pointer discipline is similar enough for things to work properly.
- Here's why. I designed the FILE structure in C so that the macros getchar and
- putchar could expand to inline code that is reasonably small and generally
- very fast. The input and output streams are each controlled by a triple of
- pointers to characters. The C++ equivalent of the macro getchar, for example,
- is the inline function definition:
- inline int getchar() {
- return ((_Files[0]->_ Next <
- _Files [0]->_Rend
- ? *_Files[0]->_Next++:
- fgetc(_Files[0])));
- }
- Compare this code with the definition of the similar streambuf public member
- function sgetc:
- int sgetc() {
- return (gptr() != 0 &&
- gptr() < egptr()
- ? *_Gn() : underflow());
- }
- The only real difference in protocol is a small one. getchar can assume that
- its "next" pointer _Files[0]->_Next is never a null pointer. It certainly
- doesn't hurt for sgetc to make the extra test.
- What typically happens when extracting from an input stream is pretty much
- what you'd hope for. If the buffer is empty, sgetc calls underflow. The
- overriding definition in class filebuf rediscovers that the buffer is empty
- and calls fgetc to supply a single character. Fortunately, fgetc often
- delivers up a whole buffer full of additional characters in the bargain.
- The next several hundred calls to sgetc simply exercise inline code that
- accesses the stored character value directly from the buffer and updates the
- "next" pointer to note its consumption. This is the same pointer as is used by
- getchar, either as a macro or an inline function definition. It is also, of
- course, the same pointer as is used by fgetc. Thus, tight synchronization is
- maintained across all flavors of input. Equally important, many character
- extractions within istream extractors have no function-call overhead
- whatsoever.
- Of course, the same rules apply to out-put streams. Most characters inserted
- by streambuf::sputc get stored directly into the output buffer, with the
- "next" pointer suitably updated. Thus, many character insertions within
- ostream inserters also avoid function-call overhead. Quod erat demonstrandum.
- Any implementation of the Standard C library that follows this discipline for
- FILE pointers can benefit from the same performance improvement. You probably
- have to change the pointer member names in the definition of filebuf::_Init.
- Nothing else need change, however.
-
-
-
- The Remaining Code
-
-
- Listing 3 shows the file fiopen.c, which shows the member function
- filebuf::open. It maps the mode argument, of type openmode, to the equivalent
- mode string expected by the function fopen. If that function succeeds in
- opening the file, it reinitializes the filebuf object to control the
- associated FILE object.
- All the remaining source files needed to implement the header <fstream> are
- trivial. Listing 4 shows the file ifstream.c, which defines the destructor for
- class ifstream. Listing 5 shows the file ofstream.c, which defines the
- destructor for class ofstream. Listing 6 shows the file stdiobuf.c, which
- defines the destructor for class stdiobuf. Listing 7 shows the file
- istdiost.c, which defines the destructor for class istdiostream. And Listing 8
- shows the file ostdiost.c, which defines the destructor for class
- ostdiostream.
-
-
- Testing <fstream>
-
-
- Listing 9 shows the file tfstream.c. It tests the basic properties of the
- classes defined in <fstream>. It does so by manipulating a temporary file
- whose name is obtained by calling tmpnam, declared in <stdio.h>. First it
- tries to write the file, then read from it, then intermix reads and writes. It
- also performs some modest file-positioning operations along the way. Finally,
- it repeats a few of these operations using the classes istdiostream and
- ostdiostream.
- If all goes well, the program prints:
- SUCCESS testing <fstream>
- and takes a normal exit. It also removes the temporary file it created.
- References
- [1] P.J. Plauger, The Standard C Library, (Englewood Cliffs, N.J.:
- Prentice-Hall, 1992).
- This article is excerpted in part from P.J. Plauger, The Draft Standard C++
- Library, (Englewood Cliffs, N.J.: Prentice-Hall, 1995).
-
- Listing 1 The file fstream
- // fstream standard header
- #ifndef _FSTREAM_____LINEEND____
- #define _FSTREAM_____LINEEND____
- #include <istream>
- #include <ostream>
- // class filebuf
- struct _Filet;
- class filebuf: public streambuf {
- public:
- filebuf(_Filet *_F = 0)
- { _Init(_F); }
- filebuf(ios::_Uninitialized)
- : streambuf(ios::_Noinit) {}
- virtual ~filebuf();
- bool is_open() const
- {return ((_File !: 0)); }
- filebuf *open(const char *, ios::openmode);
- filebuf *open(const char *_N, ios::open_mode _M)
- {return (open(_N, (ios::openmode)_M)); }
- filebuf *close();
- protected:
- virtual int overflow(int: EOF);
- virtual int pbackfail(int = EOF);
- virtual int underflow();
- virtual int uflow();
- virtual streamsize xsgetn(char *, streamsize);
- virtual streamsize xsputn(const char *, streamsize);
- virtual streampos seekoff(streamoff, ios::seekdir,
- ios::openmode = (ios::openmode)(ios::in ios::out));
- virtual streampos seekpos(streampos,
- ios::openmode = (ios::openmode)(ios::in ios::out));
- virtual streambuf *setbuf(char *, streamsize);
- virtual int sync();
- _Filet *_Init(_Filet * = 0, bool = 0);
- private:
- bool _Closef;
- _Filet *_File;
- };
- // class ifstream
- class ifstream: public istream {
-
- public:
- ifstream()
- : istream(&_Fb) {}
- ifstream(const char *_S, openmode _M = in)
- : istream(&_Fb) {_Fb.open(_S, _M); }
- virtual ~ifstream();
- filebuf *rdbuf() const
- {return ((filebuf *)&_Fb); }
- bool is_open() const
- {return (_Fb.is_open()); }
- void open(const char *_S, openmode _M = in)
- {if (_Fb.open(_S, _M) == 0)
- setstate(failbit); }
- void open(const char *_S, open_mode _M)
- {open(_S, (openmode)_M); }
- void close()
- {if (_Fb.close() == 0)
- setstate(failbit); }
- private:
- filebuf _Fb;
- };
- // class ofstream
- class ofstream: public ostream {
- public:
- ofstream()
- : ostream(&_Fb) {}
- ofstream(const char *_S, openmode _M = out trunc)
- : ostream(&_Fb) {_Fb.open(_S, _M; }
- virtual ~ofstream();
- filebuf *rdbuf() const
- {return ((filebuf *)&_Fb); }
- bool is_open() const
- {return (_Fb.is_open()); }
- void open(const char *_S, openmode_M = out trunc)
- {if (_Fb.open(_S, _M) == 0)
- setstate(failbit); }
- void open(const char *_S, open_mode _M)
- {open(_S, (openmode)_M); }
- void close()
- {if (_Fb.close() == 0)
- setstate(failbit); }
- private:
- filebuf _Fb;
- };
- // class stdiobuf
- class stdiobuf : public filebuf {
- public:
- stdiobuf(_Filet *_F)
- : filebuf(_F), _Is_buffered(0) {}
- virtual ~stdiobuf();
- bool buffered() const
- {return (_Is_buffered); }
- void buffered(bool _F)
- {_Is_buffered = _F; }
- private:
- bool _Is_buffered;
- };
- // class istdiostream
- class istdiostream: public istream {
-
- public:
- istdiostream(_Filet *_F)
- : istream(&_Fb), _Fb(_F) {}
- virtual ~istdiostream();
- stdiobuf *rdbuf() const
- {return ((stdiobuf *)&_Fb); }
- bool buffered() const
- {return (_Fb.buffered()); }
- void buffered(bool_F)
- {_Fb.buffered(_F); }
- private:
- stdiobuf _Fb;
- };
- // class ostdiostream
- class ostdiostream: public ostream {
- public:
- ostdiostream(_Filet *_F)
- : ostream(&_Fb), _Fb(_F) {}
- virtual ~ostdiostream();
- stdiobuf *rdbuf() const
- {return ((stdiobuf *)& _Fb); }
- bool buffered() const
- {return (_Fb.buffered()); }
- void buffered(bool _F)
- {_Fb.buffered(_F); }
- private:
- stdiobuf _Fb;
- };
- #endif /* _FSTREAM_ */
-
-
- Listing 2 The file filebuf.c
- //filebuf -- filebuf basic members
- #include <stdio.h>
- #include <fstream>
-
- filebuf::~filebuf()
- { // destruct a filebuf
- if (_Closef)
- close();
- }
-
- filebuf *filebuf::close()
- { // close a file
- if (_File != 0 && fclose(_File) == 0)
- { // note successful close
- _Init();
- return (this);
- }
- else
- return (0);
- }
-
- int filebuf::overflow(int ch)
- { // try to write output
- return (pptr() != 0 && pptr() < epptr()
- ? (*_Pn()++ = ch)
- : _File == 0 ? EOF: ch == EOF ? 0: fputc(ch, _File));
- }
-
-
- int filebuf::pbackfail(int ch)
- { // try to pushback a character
- return (gptr() != 0 && eback() < gptr() && ch == gptr()[-1]
- ? *--_Gn()
- : _File == 0 ch == EOF ? EOF : ungetc(ch, _File));
- }
-
- int filebuf::underflow()
- { // try to peek at input
- return (gptr() != 0 && gptr() < egptr()
- ? *_Gn()
- :_File == 0 ? EOF : ungetc(fgetc(_File),_File));
- }
-
- int filebuf::uflow()
- { // try to consume input
- return (gptr() != 0 && gptr() < egptr()
- ? *_Gn()++
- : _File == 0 ? EOF: fgetc(_File));
- }
-
- streamsize filebuf::xsgetn(char *s, streamsize n)
- { // read n characters
- return (_File == 0 ? 0: fread(s, 1, n,_File));
- }
-
- streamsize filebuf::xsputn(const char *s, streamsize n)
- { // write n characters
- return (_File == 0 ? 0 : fwrite(s, 1, n,_File));
- }
-
- streampos filebuf::seekoff(streamoff off, ios::seekdir way,
- ios::openmode)
- { // seek by specified offset
- return (streampos(_File == 0
- fseek(_File, off, way) != 0
- ? _BADOFF : streamoff(ftell(_File))));
- }
-
- streampos filebuf::seekpos(streampos sp, ios::openmode)
- { // seek to memorized position
- return (_File == 0 fsetpos(_File, sp._Fpos()) != 0
- fseek(_File, sp.offset(), SEEK_CUR) != 0
- fgetpos(_File, sp._Fpos()) != 0
- ? streampos(_BADOFF)
- : streampos(0, sp._Fpos()));
- }
-
- streambuf *filebuf::setbuf(char *s, streamsize n)
- { // provide a file buffer
- return (_File == 0 setvbuf(_File, s, _IOFBF, n) != 0
- ? 0: this);
- }
-
- int filebuf::sync()
- { // synchronize buffer with file
- return (_File == 0 ? 0 : fflush(_File));
- }
-
-
- FILE *filebuf::_Init(FILE *fp, bool closef)
- { // initialize buffer pointers
- #if _HAS_PJP_CLIB
- if (fp == 0)
- streambuf::_Init();
- else
- streambuf::_Init((char **)&fp->_Buf,
- (char **)&fp->_Next,
- (char **)&fp->_Rend,
- (char **)&fp->_Buf,
- (char **)&fp->_Next,
- (char **)&fp->_Wend);
- #else
- streambuf::_Init();
- #endif
- _Closef = closef;
- _File = fp;
- return (_File);
- }
-
- REF_DEST 9505c01.s3Listing 3 The file fiopen.c
- // fiopen -- filebuf::open(const char *, ios::openmode)
- #include <stdio.h>
- #include <fstream>
-
- filebuf *filebuf::open(const char *name, ios::openmode mode)
- { // open a file
- static const char *mods[]: {
- "r", "w", "a", "rb", "wb", "ab", "r+", "w+", "a+",
- "r+b", "w+b", "a+b", 0};
- static const int valid[] = {
- ios::in, ios::outios::trunc, ios::outios::app,
- ios::inios::binary, ios::outios::truncios::binary,
- ios::outios::appios::binary, ios::inios::out,
- ios::inios::outios::trunc, ios::inios::outios::app,
- ios::inios::outios::binary,
- ios::inios::outios::truncios::binary,
- ios::inios::outios::appios::binary, 0};
- FILE *fp;
- int n;
- ios::openmode atefl = mode & ios::ate;
- if (_File != 0)
- return (0);
- mode &= ~ios::ate;
- for (n = 0; valid[n] !: 0 && valid[n] !: mode; ++n)
- ;
- if (valid[n] == 0 (fp = fopen(name, mods[n])) == 0)
- return (0);
- if (!atefl fseek(fp, 0, SEEK_END) == 0)
- { // success, initialize and return
- _Init(fp. 1);
- return (this);
- }
- fclose(fp); // can't position at end
- return (0);
- }
-
-
-
- Listing 4 The file ifstream.c
- // ifstream -- ifstream basic members
- #include <fstream>
-
- ifstream::~ifstream( )
- { // destruct an ifstream
- }
-
-
- Listing 5 The file ofstream.c
- // ofstream -- ofstream basic members
- #include <fstream>
-
- ofstream::~ofstream()
- { // destruct an ofstream
- }
-
-
- Listing 6 The file stdiobuf.c
- // stdiobuf -- stdiobuf basic members
- #include <fstream>
-
- stdiobuf::~stdiobuf()
- { // destruct a stdiobuf
- }
-
-
- Listing 7 The file istdiost.c
- // istdiostream -- istdiostream basic members
- #include <fstream>
-
- istdiostream::~istdiostream()
- { // destruct an istdiostream
- }
-
-
- Listing 8 The file ostdiost.c
- //ostdiostream -- ostdiostream basic members
- #include <fstream>
-
- ostdiostream::~ostdiostream()
- { // destruct an ostdiostream
- }
-
-
- Listing 9 The file tfstream.c
- // test <fstream>
- #include <cassert>
- #include <cstdio>
- #include <cstring>
- #include <fstream>
- #include <iostream>
-
- int main()
- { // test basic workings of fstream definitions
- ifstream ifs;
- ofstream ofs;
- const char *tn = tmpnam(NULL);
- assert(tn != NULL);
-
- // test closed file closing
- assert(!ifs.is_open() && !ifs.fail());
- ifs.close();
- assert(ifs.fail() && ifs.rdbuf()->close() == 0);
- assert(!ofs.is_open() && !ofs.fail());
- ofs.close();
- assert(ofs.fail() && ofs.rdbuf()->close() == 0);
- // test output file operations
- ofs.clear(), ofs.open(tn, ios::out ios::trunc);
- assert(ofs.is_open() && ofs.rdbuf()->is_open());
- ofs << "this is a test" << endl;
- ofs.close();
- assert(!ofs.is_open() && ofs.good());
- assert(ofs.rdbuf()->open(tn, ios::app ios::out) != 0;
- ofs << "this is only a test" << endl;
- ofs.close();
- assert(!ofs.is_open() && ofs.good());
- // test input file operations
- char buf[50];
- ifs.clear(), ifs.open(tn, ios::in);
- assert(ifs.is_open() && ifs.rdbuf()->is_open());
- ifs.getline(buf, sizeof (buf));
- assert(strcmp(buf, "this is a test") == 0);
- streampos p1 = ifs.rdbuf()->pubseekoff(0, ios::cur);
- ifs.getline(buf, sizeof (buf));
- assert(strcmp(buf, "this is only a test") == 0);
- assert(ifs.rdbuf()->pubseekpos(p1) == p1);
- ifs.getline(buf, sizeof (buf));
- assert(strcmp(buf, "this is only a test") == 0);
- ifs.rdbuf()->pubseekoff(0, ios::beg);
- ifs.getline(buf, sizeof (buf));
- assert(strcmp(buf. "this is a test") == 0);
- ifs.close();
- assert(!ifs.is_open() && ifs.good());
- // test combined file operations
- ifstream nifs(tn, ios::in ios::out);
- ostream nofs(nifs.rdbuf());
- assert(nifs.is_open() && nifs.good() && nofs.good());
- nifs.rdbuf()->pubseekoff(0, ios::end);
- nofs << "this is still just a test" << endl;
- nifs.rdbuf()->pubseekoff(0, ios::beg);
- nifs.getline(buf, sizeof (buf));
- assert(strcmp(buf, "this is a test") == 0);
- nifs.getline(buf, sizeof (buf));
- assert(strcmp(buf, "this is still just a test") == 0);
- nifs.getline(buf, sizeof (buf));
- assert(strcmp(buf, "this is only a test") == 0);
- nifs.close();
- ofstream nnofs(tn,
- ios::in ios::out ios::ate);
- assert(nnofs.is_open());
- nnofs << "one last test" << endl;
- nnofs.close();
- // test stdiobuf operations
- FILE *fi = fopen(tn. "r+");
- { // bound lifetime of istd and ostd
- istdiostream istd(fi);
- ostdiostream ostd(fi);
- assert(fi != 0);
-
- assert(istd.buffered() == 0
- && istd.rdbuf()->buffered() == 0);
- istd.rdbuf()->buffered(0), istd.buffered(1);
- assert(istd.buffered() != 0
- && istd.rdbuf()->buffered() != 0);
- assert(ostd.buffered() == 0
- && ostd.rdbuf()->buffered() == 0);
- ostd.rdbuf()->buffered(0), ostd.buffered(1);
- assert(ostd.buffered() != 0
- && ostd.rdbuf()->buffered() != 0);
- istd.getline(buf, sizeof (buf));
- assert(strcmp(buf, "this is a test") == 0);
- p1 = istd.rdbuf()->pubseekoff(0, ios::end);
- ostd << "still one more last test" << end1;
- assert(ostd.rdbuf()->pubseekpos(p1) == p1);
- istd.getline(buf, sizeof (buf));
- assert(strcmp(buf, "still one more last test") == 0);
- }
- assert(fclose(fi) == 0 && remove(tn) == 0);
- cout << "SUCCESS testing <fstream>" << end1;
- return (0);
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Code Capsules
-
-
- Data Abstraction
-
-
-
-
- Chuck Allison
-
-
- Chuck Allison is a regular columnist with CUJ and a Senior Software Engineer
- in the Information and Communication Systems Department of the Church of Jesus
- Christ of Latter Day Saints in Salt Lake City. He has a B.S. and M.S. in
- mathematics, has been programming since 1975, and has been teaching and
- developing in C since 1984. His current interest is object-oriented technology
- and education. He is a member of X3J16, the ANSI C++ Standards Committee.
- Chuck can be reached on the Internet at 72640.1507@compuserve.com.
-
-
- You may be tired of hearing it, but let me say it one last time: you can view
- C++ in three ways: 1) as a better C, 2) as a language that supports data
- abstraction, and 3) as a vehicle for object-oriented programming. In last
- month's article I explored the "better C" features of C++. This month I'll
- show how it supports data abstraction, or, in other words, how it helps you
- create new data types.
-
-
- Abstraction
-
-
- Unlike a machine, the human mind can handle only a very limited amount of
- complexity. Life is inherently complex, and so are software systems that model
- reality. To master a complex system, it is necessary to focus on only the few
- things that matter most in a given context, and ignore the rest. This process,
- called abstraction, enables system designers to solve complex problems in an
- organized, manageable way. Grady Booch defines an abstraction as follows:
- "An abstraction denotes the essential characteristics of an object that
- distinguish it from all other kinds of objects and thus provide
- crisply-defined conceptual boundaries, relative to the perspective of the
- viewer." [1]
- Abstraction often manifests itself in software through user-defined data
- types, new classes of objects composed of built-in or other user-defined
- types. This concept is not new, of course, but languages that support it well,
- such as Ada, C++, CLOS, and Smalltalk, have only recently seen widespread use.
- If you've been programming in C for any length of time, you've probably used
- the struct mechanism. You've also had to provide functions to process those
- structures. Whether you knew it or not, you were creating a user-defined type.
- Chances are, for example, you've needed to handle dates in a program. Listing
- 1 defines a struct Date and two functions for processing dates, one to format
- a date as Month Day, Year, and one to compare dates. (See Listing 2 for the
- functions' implementation and Listing 3 for a test program.)
- To use this abstract data type, you only need to know the protocol for its two
- functions, which constitute its interface. You don't need to know how the
- functions were implemented, or even the structure layout. A well-defined
- abstraction enables concentration on the outside view while ignoring
- implementation details. This barrier between interface and implementation is
- called encapsulation. There is a hole in type Date's encapsulation barrier, of
- course, because it's possible to bypass the interface and directly manipulate
- one of the structure members, as in
- dp->month = 7;
- That hole can be closed up, since the interface itself doesn't need to know
- anything about a Date, it just needs a pointer to it. In Listing 4 I define
- Date as an incomplete type -- i.e., I just state that it exists and nothing
- more. A client program sees only date2.h, and therefore knows nothing about a
- Date's layout or implementation. I complete the type in the implementation in
- Listing 5 (the test program is in Listing 6). This extra measure of protection
- costs something, however. Programs using Date must now call explicit create
- and destroy functions, and must access all Date objects through pointers only.
- C++ improves support for user-defined data types in at least two ways: 1) by
- allowing definition of the interface functions within the scope of the class,
- and 2) by barring client access to implementation details via a language
- keyword, as opposed to the incomplete type trick. The Date class in Listing 7
- and Listing 8 differs from that in Listing 1 and Listing 2 in the following
- ways:
- 1) The data members are private, so client programs can't access them directly
- (only the member functions can).
- 2) The interface functions are public member functions, so they can only apply
- to Date objects, and the function names do not pollute the global namespace.
- The date_ prefix is no longer necessary, since the operations belong to the
- class. Note that format and compare are const member functions, meaning that
- they promise not to alter a Date's data members.
- 3) A constructor replaces C's structure initialization syntax.
- 4) The text representations for the month names now appear in the scope of
- struct Date (being static members).
- The sample program in Listing 9 shows that member functions are invoked with
- the usual dot operator for structure members.
- If it bothers you that a client programmer can still see the layout of your
- data members -- albeit without direct access -- you can hide the data member
- declarations in a new incomplete type, but this time with absolutely no impact
- to the interface (see Listing 10 and Listing 11). To demonstrate this, I
- define the DateRep helper class with a constructor, and for efficiency and
- simplicity I give the Date class direct access to the its data members by
- making it a friend to DateRep. The Date constructor now needs to create its
- associated DateRep object on the heap, so a destructor is needed to free the
- heap memory. Only the header file has to change in the test program in Listing
- 9. With a well-defined abstraction, a change in implementation will not
- inflict change on client code.
-
-
- Operator Overloading
-
-
- You can make user-defined types nearly as convenient to use as built-in types
- by adding operator functions to your class definition. In Listing 12 through
- Listing 14 I've added the six relational operations and a stream inserter for
- easy output. For efficiency I've also defined the smaller functions inline
- within the header file. Note also the declaration of ostream as an incomplete
- type in the header file. Since an ostream object appears there only by
- reference, date5.h does not have to be dependent on the iostream.h header,
- which is one of the largest in the Standard C++ library. The implementation of
- operator<< shows that defining a stream inserter usually just reduces to
- inserting an object's members into the stream. I simulate the new C++ Boolean
- data type, bool, with the definitions in Listing 15.
- As the definition of the Person class in Listing 16 shows, you can compose a
- new type from other user-defined types, as well as built-in types. Each Person
- has a birth date, which is a Date object wholly contained within a Person
- object. The Person constructor passes the Date information on to its Date
- subobject via an initialization list. The initialization list follows the
- colon in the function header, as defined in the implementation file (Listing
- 17). Notice how Person::operator<< implicitly uses Date::operator<<. Class
- Person also uses the new C++ standard string class for its text data members.
- (Borland C++, the compiler I used for most of these examples, defines the
- string class in the header (cstring.h>).
-
-
- Concrete Data Types
-
-
- C++ support for data abstraction, and operator overloading in particular, can
- make user-defined data types as convenient to use as built-in types. What are
- typical operations performed on built-in types? Programs can initialize them,
- assign them to other objects of compatible type, and pass them to or receive
- them back from functions. In C++, you can perform these same actions with your
- own types, as long as either you or the compiler provide certain special
- member functions. The presence of these member functions constitutes a
- concrete data type, one that behaves intelligently wherever a built-in type
- does. These functions include the following:
- copy constructor
- default constructor (and
- others as needed)
- assignment operator
- destructor
- Whenever an object is created, a constructor is called to initialize it. The
- initializer arguments must match a constructor's parameters in number and
- type. The default constructor is the one that takes no arguments. Another
- special constructor, called the copy constructor, initializes a new object
- from an existing object of the same type, and has the signature T(const T&),
- or T(T&), for some type T. This constructor executes whenever you pass or
- return an object by value, or explicitly initialize a new object from an old
- one, as with y in the following example:
- T x;
- ...
- T y = x; // same as "T y(x)"
- The assignment operator executes whenever you assign the value of an object to
- an existing one, and has the signature
-
- T& T::operator=(const T&)
- Note the assignment statement in Listing 18 (p3 = p2;). I did not explicitly
- define Person::operator=(), but it seems to have worked anyway. This is
- because the compiler generated it for me. The absence of operator=() in the
- class definition caused the compiler to generate one that does memberwise
- assignment, like this:
- Person& Person::operator=(const Person& p)
- {
- last = p.last;
- first = p.first;
- birth = p.birth;
- ssn = p.ssn;
- return *this;
- }
- Similarly, if you do not supply a copy constructor, the compiler generates
- this one:
- Person::Person(const Person& p)
- : last(p.last), first(p.first), birth(p.birth),
- ssn(p.ssn)
- {}
- which, as you can see, calls the copy constructor for each member object.
- If you define any constructor other than a copy constructor, then the compiler
- will not generate a default constructor for you. If you don't define any
- constructors (other than copy constructors) the compiler will generate a
- default constructor which will invoke the corresponding default constructor
- for all user-defined member objects. A compiler-generated destructor calls the
- destructors associated with any user-defined member objects.
- The compiler-generated assignment operator and copy constructor for Person
- worked because the standard string class provides its own version of these two
- functions, and because the Data class data members are built-in, so its
- compiler-generated constructors and operators suffice. In general, whenever
- the state of an object is contained entirely within the object itself, you do
- not need to override the compiler-generated member functions.
- The Person class in Listing 19 and Listing 20 stores its text data on the
- heap. A compiler-generated assignment operator or copy constructor will only
- copy pointers to the receiving object, when what it really needs to do is
- allocate space on the heap for the duplicated text. When I execute the test
- program on this class, Metaware's C++ gives the following output on Windows
- NT:
- p1 == {Richardson,Alice,[December 16, 1947], 123-45-6789}
- p2 == {Doe,John,[Bad month 0, 0],}
- p1 does not equal p2
- p3 does equal p2
- ***Free(0X4C105C):X POINTER already free
- ABORTING. . .
- At program exit, the compiler calls the Person destructor to destroy p3, which
- frees the text memory on the heap. Since p2 points to the same memory, the
- destructor attempts to delete it a second time when destroying p2, which is an
- error. Listing 21 and Listing 22 fix the problem by adding the missing member
- functions. The assignment operator uses the clever-but-lazy trick of
- explicitly invoking the destructor, followed by a call to placement new, which
- in this case executes the copy constructor on the current object. (For more
- information on placement new, see the Code Capsule "Dynamic Memory Management,
- Part II," CUJ, November 1994.)
-
-
- Generic Types
-
-
- In programming, one of the most useful abstractions is the kind that allows
- independence from data type. An example of such an abstraction is a container.
- Containers are objects that hold other objects. A set is a container that
- provides operations to insert, remove, and test elements for membership.
- Listing 23 - Listing 25 illustrate a class that implements a set type for
- integers. It uses a fixed-size array as the underlying data structure
- (although bit sets are a more efficient implementation for sets of ordinal
- values). The implementation in Listing 24 uses the find algorithm, a new
- addition to the Standard C++ library, which expects pointers to the beginning
- and to one past the end of the array as its first two parameters. If you were
- to define SetOfLong, or SetOfString, or a set for any other type supporting
- the equality operator, you would find that the only thing that changed would
- be the type of objects in the set. If you think about it, a set really
- shouldn't have to care about the type of objects it holds.
- C++'s template mechanism allows you to define a generic set type that
- essentially ignores the type of the objects it contains, by specifying that
- type as a parameter (see Listing 26). For efficiency, I use the
- dynamically-sized vector template class from the Standard C++ library in the
- implementation. Dynamic sizing is crucial when using a set of large objects,
- since a fixed-size array of the same would result in much wasted space. As you
- can see in set2.h in Listing 26, the find algorithm also works on vectors.
- This is because its begin and end member functions return an iterator, which
- is a generalization of a pointer. The erase member function expects an
- iterator argument, which find returns. The program in Listing 27 uses the Set
- template to hold Person objects.
-
-
- Summary
-
-
- Complexity is an unavoidable fact of life. Support for user-defined data types
- enables the programmer to model and manage a complex system in software. C++
- supports data abstraction via classes, member access control, constructors and
- destructors, operator functions, and templates. If you provide the suitable
- member functions, you can define abstractions that have the convenience of
- built-in data types.
-
-
- Computers In Kansas
-
-
- During the last couple of years I've enjoyed wearing the official T-shirt of
- C/C++ Users Journal. On the back it says, "Yes, there really are computers in
- Kansas." Not only are there computers, but there is a group of talented and
- pleasant individuals who comprise R&D Publications. In January they sponsored
- "C/C++ Solutions '95," a practical, "how-to" seminar in Kansas City.
- (Missouri. All right, it's not in Kansas, but it's close.) We had ten of the
- best C/C++ developers and teachers you will find anywhere, including Scott
- Meyers, author of "Effective C++," and our own Dan Saks. The balance of
- material and the emphasis on C as well as C++ was very well received. Sorry if
- you missed it. Stay tuned -- we'll do it again. You won't get this type of
- intensive training at any other seminar.
- I've also enjoyed contributing as a columnist for this journal since October
- 1992. This has been a valuable outlet for me to share some of my learnings as
- a C/C++ developer and contributor to the C++ standard. Due to personal time
- constraints, I reluctantly announce that this is my last column. I've
- appreciated your feedback and support, and will miss you and the folks at R&D.
- Goodbye, farewell, and keep on hacking.
- Reference
- [1] Grady Booch, Object-oriented Analysis and Design with Applications, Second
- Edition (Benjamin-Cummings, 1994).
-
- Listing 1 A Date type in C
- /* date.h */
-
- struct Date
- {
- int month;
- int day;
- int year;
- };
- typedef struct Date Date;
-
-
- char *date_format(const Date *, char *);
- int date_compare(const Date *, const Date *);
-
- /* End of File */
-
-
- Listing 2 Implementation for the Date type
- /* date.c */
-
- #include <stdio.h>
- #include "date.h"
-
- static const char *month_text[] =
- {"Bad month", "January", "February",
- "March", "April","May", "June", "July",
- "August", "September", "October",
- "November", "December"};
-
- char *date_format(const Date *dp, char *buf)
- {
- sprintf(buf,s %d, %d",
- month_text[dp->month],dp->day,
- dp->year);
- return buf;
- }
-
- int date_compare(const Date *dp1,
- const Date *dp2)
- {
- int result = dp1->year - dp2->year;
- if (result == 0)
- result = dp1->month - dp2->month;
- if (result == 0)
- result = dp1->day - dp2->day;
- return result;
- }
-
- /* End of File */
-
-
- Listing 3 Tests the Date type
- /* tdate.c */
- #include <stdio.h>
- #include "date.h"
-
- #define DATELEN 19
-
- main()
- {
- Date d1 = {10,1,1951}, d2 = {3,7,1995};
- char buf[DATELEN+1];
- int cmp;
-
- printf("dl == %s\n",date_format(&d1,buf));
- printf("d2 == %s\n",date_format(&d2,buf));
- cmp = date_compare(&d1,&d2);
- printf("d1 %s d2\n",
- (cmp < 0) ? "precedes"
-
- : (cmp > 0) ? "follows"
- : "equals");
- return 0;
- }
-
- /* Output:
- d1 == October 1, 1951
- d2 == March 7, 1995
- d1 precedes d2
- */
-
- /* End of File */
-
-
- Listing 4 A safer Date type
- /* date2.h */
-
- /* Declare the incomplete Date type */
- typedef struct Date Date;
-
- Date *date_create(int, int, int);
- char *date_format(const Date *, char *);
- int date_compare(const Date *, const Date *);
- void date_destroy(Date *):
-
- /* End of File */
-
-
- Listing 5 Implementation for Listing 4
- /* date2.c */
-
- #include <stdio.h>
- #include <stdlib.h>
- #include "date2.h"
-
- struct Date
- {
- int month;
- int day;
- int year;
- };
-
- static const char *month_text[] =
- {"Bad month", "January", "February",
- "March", "April", "May", "June", "July",
- "August", "September", "October",
- "November", "December"};
-
- Date *date_create(int m, int d, int y)
- {
- Date *dp = malloc(sizeof(Date));
- if (dp == NULL)
- return NULL;
-
- dp->month = m;
- dp->day = d;
- dp->year = y;
- return dp;
- }
-
-
- char *date_format(const Date *dp, char *buf)
- {
- sprintf(buf,"%s %d, %d",
- month_text [dp->month],dp->day,
- dp->year);
- return buf;
- }
-
- int date_compare(const Date *dp1,
- const Date *dp2)
- {
- int result = dp1->year - dp2->year;
- if (result == 0)
- result = dp1->month - dp2->month;
- if (result == 0)
- result = dp1->day - dp2->day;
- return result;
- }
-
- void date_destroy(Date *dp)
- {
- free(dp);
- }
- /* End of File */
-
-
- Listing 6 Tests the new Date type
- /* tdate2.c */
- #include <stdio.h>
- #include "date2.h"
-
- #define DATELEN 19
-
- main()
- {
- Date *d1 = date_create(10,1,1951),
- *d2 = date_create(3,7,1995);
- char buf[DATELEN+1];
- int cmp;
-
- printf("d1 == %s\n",date_format(d1,buf));
- printf("d2 == %s\n",date_format(d2,buf)):
- cmp = date_compare(d1,d2);
- printf("d1 %s d2\n",
- (cmp < 0) ? "precedes"
- : (cmp > 0) ? "follows"
- : "equals");
- date_destroy(d1);
- date_destroy(d2);
- return 0;
- }
-
- /* Output:
- d1 == October 1, 1951
- d2 == March 7, 1995
- d1 precedes d2
- */
-
-
- /* End of File */
-
-
- Listing 7 The Date type in C++
- // date3.h
-
- struct Date
- {
- Date(int, int, int);
- char *format(char *) const;
- int compare(const Date &) const;
-
- private:
- int month;
- int day;
- int year;
-
- static const char * month_text[13];
- };
-
- /* End of File */
-
-
- Listing 8 Implementation for the Date class
- // date3.cpp
-
- #include <stdio.h>
- #include "date3.h"
-
- const char * Date::month_text[13] =
- {"Bad month", "January", "February",
- "March", "April", "May", "June",
- "July", "August", "September",
- "October", "November", "December"};
-
- Date::Date (int m, int d, int y) :
- month(m), day(d), year(y)
- {}
-
- char * Date::format(char *bur) const
- {
- sprintf(buf,"%s %d %d",
- month_text[month],day,year);
- return buf;
- }
-
- int Date::compare(const Date & dp2) const
- {
- int result = year - dp2.year;
- if (result == 0)
- result = month - dp2.month;
- if (result == 0)
- result = day - dp2.day;
- return result;
- }
- // End of File
-
-
- Listing 9 Illustrates the Date Class
-
- //tdate3.cpp
- #include <stdio.h>
- #include "date3.h"
-
- #define DATELEN 19
-
- main()
- {
- Date d1(10,1,1951), d2(3,7,1995);
- char buf[DATELEN+1];
- int cmp;
-
- printf("d1 == %s\n",d1.format(buf)