Letters to the editor may be sent via email to [email protected], or via the postal service to Letters to the Editor, C/C++ Users Journal, 1601 W. 23rd St., Ste 200, Lawrence, KS 66046-2700.
Dear CUJ,
There was a time when I felt discouraged. Studying Pascal was definitely not in my plans. I hated Pascal. I never got comfortable with it. I wrote all my assignments in C and then struggled to convert them back to Pascal. I bought the Turbo Pascal compiler so that I could test my programs at home, only to find out that Turbo Pascal was not considered "real" Pascal. My teachers were appalled by the assumption that I could write a program that would compile on two different machines. When I pointed out that C could compile on every machine that I used they banned all such talk from the classroom, we were studying Pascal and there was to be no backtalk. Assembly language was no better for me. I was frustrated and on the verge of dropping out of college entirely.
Then I remembered one of my favorite P.J. Plauger columns. I don't even remember what magazine it was written for but I can still see the page layout in my mind. The column showed how the 64K page limit of the 8086 line of chips was a deliberate design decision to support Pascal functionality. Pascal was going to be the language of the future. The column went on to explain that C wasn't even on the radar when the chip was designed. This column had saved me once when I bumped against the 64K limit, now it saved my college career. I finally understood that Pascal was the college's choice because all the graduate level tests were in Pascal.
I thought about what we were learning. I had come to school to learn about Finite State Automata, compiler design theory, and other Unix programming concepts. Instead I learned how to find characters in strings. Except there was no such thing as a string in Pascal, at least not in the "standard" way that C used. Pascal had no string library, everything had to be created. I scraped by as the most miserable Pascal programmer in college history. My Pascal programs worked but they were masterpieces in inefficiency and over-achievement. I recreated most of the C library in Pascal and used something from my private library every time I approached a new problem. I just could never break the habit of wanting to use standard functions that could manipulate generic data.
I still work in computers, in spite of that experience. My real computer education, the reason I stayed in computers, was because I was reading the columns of Alan Holub and P.J. Plauger. The columns were examples of such great writing that I wanted to continue to work in the field just so that I could continue to enjoy the experience of reading and understanding the issues.
It seemed so effortless to me then. I had an "aha" experience on almost every column that I read. I bought all the books, subscribed to all the magazines, and read everything that I could find that Mr. Plauger wrote. I've tried to do my own writing about computer topics. It's agony to my readers. I have to explain concepts over and over before I can get ideas simple enough to make sense. When I'm done the quality is a pale reflection of the work that Mr. Plauger has done. I want to be clear and lucid, instead it is muddy and complicated, and full of computer concepts and jargon that only other programmers can understand.
The greatest writers, in my opinion, are those who can write about technical subjects and make them understandable, keep the ideas in the forefront, and the jargon in the background. I can't begin to tell how many times I've called my friends and family and told them about the latest greatest article I've read in the C/C++ Users Journal. Those articles always had one name on them, P.J. Plauger. Okay, truth to be told, those not working in computers don't always share my appreciation. But the clarity of the ideas in the articles allows me to start great conversations.
Programmers are obsessed with time. I once went to a computer conference and checked the watches of everyone that I could see. The hours were different reflecting the various regions of the country they had come from, but the minutes and seconds were all set the same. And yet time is one of the most complicated functions, calendars are not easy to get correct (witness the Y2K fiasco.) Some of my favorite Plauger columns involved time and calendar discussions. Just the idea that time is an area where caution should be applied has saved me enormous grief.
There are four lessons I learned from Mr. Plauger. One, simplicity is the name of the game. Two, try to reuse other people's code, especially if it is in a library. Three, keep reworking code, there is always something to learn by trying to make the code the best it can be. I hope that I don't have to write a sort routine, but I got a great charge out of reading the latest set of columns about sorts and queues.
My latest lesson was the hardest. Many years ago I swore that I would never work in Fortran, assembly language, or embedded systems. Last month I agreed to work on systems that were in Fortran, assembly language, and dealt with embedded systems. Avoiding the work didn't make it less painful, the work needed to be done anyway. My first move after I learned that I would be working in these areas was to go to my bookshelf and pull out my P.J. Plauger books and columns.
I will miss the monthly joy of reading the latest Plauger column. But I'll be first in line at the bookstore for the next book. (Explaining STL, I hope.) As a C++ user I'll use his library. And I'll try to stay current in my chosen field in case he writes another column.
My thanks for a great ride. I raise a glass to you for the years of excellent writing that you have provided. And maybe someday I will be able to put my name on an article in a magazine that will make someone jump up and run from the room yelling as I have done so many times, "Hey, the latest article by P.J. Plauger is so interesting. You have got to read it!"
Darrel W. Riley
[email protected]
Thanks for the kind words. Looks to me like you're well on your way to learning how to express yourself effectively. pjp
Dear CUJ,
I want to thank Bill for all the fine work he has produced, not only for CUJ, but for Computer Language and Embedded Systems Programming as well. However, I take exception with the title exit(0) that was chosen for the last column.
To quote from the seminal work, The Elements of Programming Style: "Write clearly don't sacrifice clarity for "efficiency."'
Clearly the column should have been titled exit(EXIT_SUCCESS).
Best of luck in all future endeavors, and thanks again for all your help.
Regards,
Jeff
Much as I hate to quibble with praise, I have to point out that a zero argument to exit is a portable, if less obvious, way to terminate reporting success. Thanks. pjp
Dear CUJ,
Tony Balinski wrote a letter (Febuary 2000, p. 98) describing a method of allocating large data items in pieces on the stack by using recursive function calls, where each invocation allocates a single piece of the whole data item being processed.
I came up with an almost identical scheme when I was trying to write a lexical analyzer way back in my college days (circa 1983). I was attempting to allocate an arbitrary-size string buffer to hold the characters of any given token. Some kinds of tokens, such as character string literals, can be quite long, and I needed a way to read the entire token into memory without using inordinately large amounts of pre-allocated memory blocks.
So I hit upon the scheme essentially described by Tony, where each recursive call to a special function allocates a fixed-size character array on the stack, reads a piece of the current token being scanned, then calls itself recursively until the whole token is read. This approach comes pretty close to the goal of allocating only as much memory space as is actually needed for any given token (with a little bit of overhead for the calls and some extra space left over in the last recursive call).
I used a variation on this theme later (circa 1988) for a program that needed to keep track of mutually recursive functions which called each other down to an arbitrary depth. In this variation, I kept the pertinent information in a local (stack) structure and passed its address down to the next invocation of the function, after first saving in the local structure the previous structure pointer that was passed to the function. The result after N calls was a linked list of N structures, all residing on the stack. The last invocation of the function could then traverse the linked list to obtain information about any previous invocation of the function.
A simple code fragment illustrates this scheme:
struct Info { struct Info * prev; int seq; }; void foo(int num, struct Info *p) { struct Info inf; /* Link the list of nodes to the current node */ inf.prev = p; inf.seq = num; ... /* Search the list backwards (upwards) */ for (p = &inf; p != NULL; p = p->prev) ... /* Recursive call, adding another level to the list */ foo(num+1, &inf); ... }
Hopefully other programmers will relate stories about similar schemes.
David R. Tribble
[email protected]
http://david.tribble.com
Thanks for the tip. mb
Dear CUJ,
Regarding the May 2000 article entitled "State Machine Design in C++," a few readers pointed out some potential flaws.
First, the comments within the StateMachine::StateEngine function that indicate where semaphore protection should go is wrong. Correct placement of the semaphore lock/unlock would be at the beginning and end of StateMachine::ExternalEvent. This new location completely protects state execution whereas the original placement did not.
Second, Microsoft Visual C++ 6 gives a runtime error regarding an incorrect calling convention while executing StateMachine::StateEngine. When StateMachine::StateEngine calls a state function that uses void as the sole input argument (e.g., void Motor::ST_Idle(void)) the fault occurs.
While the code worked on some compilers, it is definitely not portable. The basic problem has to do with the state-function signatures. In the article I indicated that both void and EventData* were acceptable arguments to a state function. This is incorrect if you want portable code. Only a pointer to EventData, or a derived class thereof, is allowed. State functions that don't require event data should just pass in a pointer to base class EventData. This change ensures that when calling a state function via a pointer, they all have the same function signature.
In class Motor, making the following function signature changes fixes the portability problem:
void ST_Idle(EventData*); void ST_Stop(EventData*);
I have included three modifed source files that fix the above problems. In the headers, I included a small comment indicating how the changed code deviates from the published code.
Regards,
David Lafreniere
We have updated our web site with these new files. mb
Dear CUJ,
Writing robust efficient code is hard. Case in point: Marc Guillemot's article "Catching Untested Return Codes" (CUJ, May 2000). Mr. Guillemot presents the class ErrorCode that is designed to insure that all return codes are checked. Enforcing this common-sense guideline would be a good thing, but ErrorCode could possibly make things worse by introducing its own bugs.
Consider this contrived code fragment which makes use of the ErrorCode class:
enum ErrorCodeValue { Success, MemFail, ...}; ErrorCode Foo() { Bar* pBar = new Bar(); if(pBar) { GiveAwayABar(pBar); return Success; } return MemFail; } ErrorCode ret = Foo(); if(Success != ret) {...}
ErrorCode contains a Boolean responsibility flag which is heap based. A new Boolean value is created on the heap when ErrorCode is constructed, and then returned to the heap when ErrorCode is destroyed. The bug in ErrorCode is that this Boolean value is never checked to insure it was properly created. As a result, whenever it's used, in the copy constructor for example, it could potentially cause an exception:
ErrorCode::ErrorCode (const ErrorCode& c) : enValue_(c.enValue), pboResp_(new bool(true)) { *c.PboResp_ = false; }
Another, subtler problem, results from how ErrorCode is used. In almost all cases ErrorCode will be a stack-based automatic variable that is created and destroyed quite often. In just this short fragment of code there have been three instances of ErrorCode created and destroyed, which means we've hit the heap six times. This is going to result in very different memory and performance characteristics for debug builds, which use ErrorCode the class, and release builds, which use ErrorCode the enum. This is even more problematic for multithreaded applications as heap allocation is serialized, ignoring third-party or custom heaps such as SmartHeap. So there will be subtle differences in how threads interact between release and debug builds.
Having behavior of an application change between release and debug builds can lead to extremely hard to track down bugs much worse than not checking the occasional return code.
Fortunately, the fix for ErrorCode is trivial. ErrorCode can simply contain the Boolean flag by value instead of by reference. Marc ostensibly used a heap-based flag so that he could modify the flag in the constant instance during copy construction. But it's not necessary to go to such lengths since the flag can be made mutable so that it's never constant.
If for some strange reason you have a C++ implementation that doesn't support mutable you can resort to the old trick of casting away the constant attribute to change the value.
Mike Junkin
Marc Guillemot replies:
Dear Mike,
CUJ forwarded me your comments on my article and I want to say you that I fully agree. Since I've written the ErrorCode class and the article for CUJ last November, there are two main points I'd like to change:
- First, the pointer used for the responsability flag: shame on me but I didn't know the mutable keyword at that time. I chose to implement it with a pointer rather than using a cast because I found it was "cleaner," but as you explain it, it can lead to subtle, hard-to-find problems, so it was not such a good idea.
- Second, I think all this could be generalized and the class changed to a template to allow its use with different custom types.
Another CUJ reader suggested to me that I use an int() conversion operator rather than operator== and operator!=. I've not had time to look for what drawbacks it brings but I believe it could permit the testing of an ErrorCode in a switch statement.
Sincerly,
Marc Guillemot
Dear CUJ,
I read the article "Using Predicate Waits with Win32 Threads" by David M. Howard (May 2000) with great interest. The producer/consumer pattern is one of the most useful thread synchronization schemes. However I've found one fragment of code in Listing 3 slightly confusing.
A thread executing the put method waits on a semaphore by calling WaitForSingleObject. When the semaphore is released, it tries to acquire the critical section by calling EnterCriticalSection. The comment between these two lines correctly states that there could be several threads released at once. My understanding is that this can happen when the semaphore count is decreased by more than one,
- either by some other thread calling ReleaseSemaphore with a count different from one (not in this particular program),
- or by some other thread releasing the semaphore while there already is a producer thread preempted between WaitForSingleObject and EnterCriticalSection.
In any case, the number of threads between those two lines should always be less or equal the number of available slots in the queue, because each such thread was released by a call to ReleaseSemaphore, which gurantees the availability of (additional) slots.
My question is then: why, within the critical section, the condition
if(m_qData.size()<m_qData.max_size())
is checked (rather than asserted). It seems to me that if this condition were false, the whole scheme wouldn't work correctly a call to put would drop the data item without any indication to the caller that it wasn't successfully added to the queue. Is this just overly defensive programming or is there some quirk in the Win32 API that makes this test necessary?
By the way, the same question applies to the implementation of the get method.
Bartosz Milewski
www.relisoft.com
David Howard replies:
Dear Mr. Milewski:
Thanks for your interest in the article and the thoughtful analysis.
You are correct that the condition checking for space in queue should be an assert because if everything is working right the condition can't happen, as your analysis explains. It isn't a Win32 issue. My check for space at that point was a holdover from the earlier code that required the check.
David Howard
Principal Software Engineer
Sierra Nevada Corporation
444 Salomon Circle
Sparks, NV 89434
phone: 775.331.0222
fax : 775.331.0222
[email protected]