Kenneth Pugh, a principal in Pugh-Killeen Associates, teaches C and C++ language courses for corporations. He is the author of C Language for Programmers and All On C, and was a member of the ANSI C committee. He also does custom C programming for communications, graphics, image databases, and hypertext. His address is 4201 University Dr., Suite 102, Durham, NC 27707. You may fax questions for Ken to (919) 489-5239. Ken also receives email at kpugh@dukemvs.ac.duke.edu (Internet) and on Compuserve 70125,1142.
Q
Some of my co-workers and I got in a discussion recently about the postfix increment operator and prefix increment operator. Could you explain what the resulting value will be in the following example:
int X = 5; X = x++ + ++x;Borland C++ v3.1 yielded a result of 13, and cc on a BSD v4.3 machine yielded the same results. The discussion has been quite spirited and I'd really like to know what the "correct" result of this expression is (if there is any). I say that the ++X is pre-incrementing the variable X to 6 and then it is adding that to X, which is 6 due to the previously-evaluated expression ++X. Then the two values are added together and assigned to X yielding a value of 12, after which that value is evaluated with the post-increment X++ to yield the final value of 13. Is this correct? Can this expression be evaluated to a known value?Louis Zirkel III
Salt Lake City, UTA
I was just out in Utah teaching C++ at Novell and trying out slickrock biking in Moab. This is a slicky-tricky expression. The expression will be evaluated to a value, unfortunately one that is undefined. It may appear that it is definable, since it will probably work the same way on a particular hardware/software combination. There are two reasons why this expression is undefined. The expression contains side effects and the order of evaluation of an expression is not specified. Let's examine these separately.
According to the ANSI standard, a side effect is a change in the state of the execution environment. Modifying an object, accessing a volatile object, modifying a file, or calling a function that does any of these operations are all side effects. A side effect that is the result of the evaluation of an expression will be complete at a sequence point. The end of a full expression and the comma operator are sequence points. In your example:
int X = 5; X = X++ + ++X;the prefix increment, the postfix increment, and the assignment operators all have side effects. Let's shorten the example to:
int X = 5; X = X++;By the side effect rule, the postfix increment in X++ will be applied to the value in X by the time the semicolon is reached. Exactly when it will be applied is not specified. X could be incremented before or after the assignment is made to X. The value that is assigned will be 5. However, the final contents of X may either be 5 (if the increment is done before the assignment) or 6 (if the increment is afterwards). The order of evaluation of sub-expressions except for &&, | |, ?:, comma, and function call is unspecified. The decision as to which side of a binary operator is evaluated first remains with the compiler. To simplify your example:
int X = 5; X++ + ++X;Whether X++ or ++X is evaluated first is not specified. A particular compiler may do it the same way every time, but not even that is guaranteed. If the right-hand side is evaluated first, you will get one of two values, depending on when the increment takes place. If it is immediate, then you will get 6 + 7, or 13, which is the value you got. If it is delayed until the end, you will get 5 + 6, or 11.If the left-hand side is computed first, the pre-increment will occur, so you will get 6 + 6, or 12. For those who really want to consider another possibility, the pre-increment might only happen to a temporary register and not to X's memory location. This would make this computation 5 + 6, or 11. I'm sure you can come up with some more possibilities, especially with the assignment operator thrown in.
I use an example similar to this in my C classes. As an indication of the uncertainty, I have the students decide on what is the "right" value for the expression. Then they run a program to determine the computer's value. I have not recorded all the results, but about 50 percent of the time the students are wrong. And the value is not the same on all computers.
The ANSI standard states that between the previous and next sequence point, an object shall have its stored value modified at most once by the evaluation of an operand. The prior value shall be accessed only to determine the value to be stored. If this rule is violated, the result is undefined. This rule sort of combines both the side effect and the order of evaluation rules.
The example you gave is a fairly obvious violation of these rules. Subtler errors include statements as:
array[i++] = i;or
array[i++] = array[i] + 1;One may think that the left-hand side of the assignment is somehow always computed after the right-hand side. However, the assignment is just another binary operator for which the order of evaluation is unspecified. You should note that if the latter expression is changed to:
array[i++] += 1;then there is no indefiniteness. The variable i appears only once in this expression.I'll mention one other aspect of side effects. If your program handles signals, only the values of objects as of the previous sequence point may be relied upon. For example, there is no telling what the value of X will be if the expression evaluation was interrupted.
Modem Control
Regarding the letter published in the May 1993 (Vol. 11.5) issue of the C Users Journal from Andrew W. Ackard, our company's products are also primarily communications related. Whether using an embedded controller, MS-DOS, MS-Windows, or OS/2, we have run into the same problem that Mr. Ackard has described, namely, incompatible modems.A reference work that we have found to be most useful, although seriously dated, is The C Programmers Guide to Serial Communications by Joe Campbell; copyright 1987, published by Howard W. Sams & Company, ISBN #0-672-22584-0.
We have found this book to be an invaluable tool in using serial communications. Although it only lists the commands for Hayes Modems (Models 300, 1200, 1200B, 1200+, 1200B+, 2400, and 2400B), it is still useful. It also gives a very good discussion of basic serial communications concepts, cabling, portability concerns, and UART Programming (8250 and Z80SIO).
So far, even though it's dated, this is still the best book I've found on the subject. We have also discovered that most modem manufacturers are more than willing to send (via fax or mail) a copy of their various "AT" and S-register commands. A call to a hardware distributor will usually land you the names and phone numbers of all of the manufacturers they carry, and then you're usually just a phone call away.
Thanks for an excellent column.
Rob Hedin
FloridaThanks for your reply. It sounds like there's the potential for a library that handles the differences in modem control. Perhaps each modem manufacturer could provide a "modem driver" that would perform a standard set of commands. A user would not have to read what the command sequence is to do touch-tone dialing. He would simply load the manufacturer's driver with his communication program.
This driver might be a bit lengthier than a standard device driver, but it would help the developer as well as the user. Of course there may always be a few operations that do not make sense to support in the general world. Just as screen designers went past MS-DOS to get to the BIOS or memory for higher speeds, so might a few communication developers go directly to the modem hardware. One of the features that these drivers could support is built in transmit and receive buffering. This could be implemented regardless of whether the hardware actually provided it.
Documentation of Classes
I am having trouble deciding how to document inherited classes. Should I include all the inherited functions in the documentation for the derived class or should I simply make a reference back to the base class.Bill Smith
New York City, NYI have seen class documentation go both ways. And whichever way it appears for a particular class library, I like it the other way, of course. This relates to the naming conventions that have been discussed in previous columns. How much information do you repeat in a name, or in this case, in the description of a class?
I feel that having a full listing of functions for each class is desirable. A class user should not have to trace up the entire inheritance tree to find out what functions are available for a particular class. The functions do not have to be described in detail, but references back to a single full explanation can be made. If the function name and parameters do not adequately describe its use, then a description of their purposes should be included in the listing.
One gross omission with a user interface package that I have been reviewing is the lack of documentation on the over-all class model. This particular package derives a number of classes from other classes. A nice description of how the classes interrelate, and not just an inheritance diagram, would be valuable in rapid understanding of the overall structure.
On the other hand, the manual for this particular package repeats for every object the same two pages of full definition for some parameter values that are common to almost all the objects. This repetition makes it harder to find those features that are unique to an object.
There is a coincidental aspect of how C++ objects are documented that I find appealing over normal manuals. Rather than alphabetically arranged manuals, I prefer ones which are grouped by functional usage. For example, MKDIR, RMDIR, and CD should be next to one another, so that I do not have to page flip to find the see alsos. I can always use an index to find the page for a particular function whose name I know. But finding the function by purpose tends to be difficult. With C++, functions are grouped by class. So related functions tend to lie in proximity with each other. Since you are writing documentation, I will make a strong suggestion. The index is the most important part of the book. There are several well-known programs that have probably the worst indices that I have ever run across. They are simply pagination tables for commands and text headers. Look in a database program index and try to find a term that would lead to something simple like clearing the screen. Try Screen, clearing or Clearing the screen. Nothing. Okay, how about Display, clearing or Clearing anything. Still nothing. Erasing maybe. No. You get my point. An index should include additional keys in order to make it easy to find desired information.
Double Pointers
A reader has asked the purpose of double pointers. Double pointers are variables which are used to point to pointer variables and are declared as:data_type **variable;
There are at least two basic uses for double pointers. The first is to receive the address of a variable which is a pointer. The second is as a handle for objects that are allocated. Some people use a double pointer in a fashion similar to using pointers for array parameters. For example, you have seen:
int integer_array[10]; function(integer_array);with a called function:
function(int *integer_array) { ... }as an alternative to:
function(int integer_array[]) { ... }I prefer the latter form for clarity, because it is apparent that the function is going to expect something that has multiple elements. I used the former form if the function only expects an address of a single element. There are some thoughts circulating as to whether arrays should be first-class objects and therefore passable by value. The latter form would be used if the array was a first-class object. I will leave that discussion to a future column.If the array that is being passed is an array of pointers as
int *integer_array[10]; function(integer_array);then the code would look like either:
function(int **integer_array) { ... }or
function (int *integer_array[]) { ... }As far as the compiler is concerned, either form works exactly the same. You may have noticed that programmers will use either char **argv or *argv[] as the second argument to main. Once again I prefer the latter, as it more clearly matches my mental representation of the parameter.I would use a double pointer unquestionably if the operation of the function required changing the value of a parameter which was an address. The simplest example is one that might be used to parse an input string one token at a time. For example, you might have variables that look like:
char buffer[] = "ABC,DEF.GHI"; char * pointer_to_char; char * starting_char; char delimiters[] = ",.\t";You want to parse the contents of buffer into tokens that end with the delimiters. There could be a function called parse that looks through the character string it is passed for the delimiters. The calling program might look like:
pointer_to_char = buffer; starting_char = buffer; do { index = parse(&pointer_to_char, delimiters); switch(index) { case ',': /* Do appropriate actions */ } starting_char = pointer_to_char; } while (index >= 0);Note that the parse function needs the address of the pointer so it can modify the value. That function's header would look like:
int parse(char ** pointer_to_pointer_to_char, char delimiters[]) /* Return index of delimiter or < 0 if end of string */ /* Alter the pointer whose address is passed to point to the next character after the delimiter (or to the terminating NUL) */ { /* Contents */ }The other major instance of double pointers is to be able to perform garbage collection on allocated memory. The standard allocation routines return a pointer to a block of memory. Once that memory has been allocated, the operating system cannot relocate that memory, as your program will always be making a direct reference to it.Suppose the operating system returns to you a double pointer to memory, usually called a handle. What you receive is the address of a variable that points to the actual memory location. In order to make a reference to actual memory, you use two indirection operators on that handle.
Now the operating system can change the location of the actual memory that is allocated. Of course it cannot change the address at which it is storing that location (the value which it returned to you as the handle). The time at which the operating system can move the allocated memory is system-dependent. It will not (or should not) do it while you are referring to that memory. You may have to lock your handle while you use it in order to avoid referencing moving memory.
The coding technique for using a handle might look like the following. This assumes the name for the system routine is handle_allocate:
typedef void **HANDLE; int *int_pointer; HANDLE a_handle = handle_allocate(memory_size); int_pointer = *a_handle; // Use int_pointer as normal pointerI haven't run across the need for triple pointers, although I have seen code that uses them. If someone needs that level of complexity, the problem might be reorganized to cut references down to double pointers.