Letters to the editor may be sent via email to cujed@rdpub.com, 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 Editor,
I just read "Interrupt Thunking" by David D. Taylor in the March issue of C/C++ Users Journal.
I spend too much of my time attempting to teach junior Software Engineers about interrupts to let this article go unchallenged. I would never have expected such an article to be published by C/C++ Users Journal. It is patently wrong, both implicitly and explicitly.
Whether or not a bit of "test code" actually works is immaterial. One can never call C Code and use its library functions from an Interrupt Service routine. I want to make this perfectly clear. Never.
There is much confusion about interrupts and Interrupt Service Routines, amongst so-called Software Engineers when, if they had been properly taught, no such confusion should exist at all. This may stem from the fact that they may have seen sections of kernel code, in multitasking operating systems, written with apparent reckless abandon in C.
What is not understood is that this C Code is executed after a context switch. What may be further misunderstood is the difference between a Context Switch and an Interrupt.
In a multitasking operating system's kernel, when an interrupt occurs, the kernel gets control and saves the entire operating environment of any task that may have been interrupted. This means that not only are any processor registers saved, but also the state of any shared hardware such as a Floating Point Unit (FPU), etc.
Further, since a user's task never touches any hardware, and since the kernel always queues and completes any hardware I/O itself, there are never any hardware states to save.
And further, since even C runtime library functions are executed by the kernel, on behalf of a user's task, there are no states to save there either.
Once the interrupted user's state is saved, the kernel sets up its own environment from its own previously-saved state. This allows the kernel to do anything it wants. It has its own stack, its own data areas, and access to all the hardware. It may call some C function to service a pending interrupt or, most likely, make a note that an interrupt is pending then perform a context switch to a higher priority task.
Eventually, perhaps after several additional context switches, caused by the preemption timer, the kernel will get around to servicing the interrupt by calling the, not-very-special, C code function called an "Interrupt Service Routine."
Now, an interrupt in a "real mode" system is quite a different story. An interrupt can (read will) occur at any time. The CPU may be performing mathematics using a FPU. It may be parsing a string in a C run-time library, perhaps printf, scanf, etc. The string routines use resources such as temporary data allocated statically, not on the stack.
Worse, MS-DOS may be writing a file or just polling for keyboard input.
If an interrupt occurs under these conditions, and the Interrupt Service Routine uses MS-DOS or a C library function that was interrupted, the results will be undefined. Actually the word "undefined" is a bit mellow. The eventual result will be incorrect mathematics, corrupt input and output, plus a trashed file system.
It doesn't do any good to just set up a special stack. Using a separate stack simply lets the writer get away with gross destruction of the operating environment for long enough to test some code.
If you want to write an Interrupt Service Routine that works correctly, stay away from the C functions like:
void interrupt(*)();Interrupts must properly handle the requirements of the hardware that caused the interrupt. This means that you can't just write 0x20 out port 0x20 after an interrupt. Such a non-specific end-of-interrupt command to the controller will eventually cancel the wrong interrupt, i.e., one that has not yet been delivered. The result will be more file corruption if the canceled interrupt just signaled Disc I/O completion.
It is not hard to learn enough assembly-language to make a proper interrupt service routine in either the ix86 or the 68000 environment. Doing so will lead to the proper insight into what they can and cannot do. Wrapping an interrupt service routine into the protective shell of a C function call will not protect you from thousands of irate customers complaining about the destruction of their file systems and "no reason" crashes.
Cheers,
Dick Johnson
Analogic CorporationThe author replies:
In reply to Richard Johnson's letter regarding my "Interrupt Thunking" article, I make the following points:
The article describes a program module that is an enhancement to the void interrupt function type, provided by C compilers. Anyone who is fundamentally against using the void interrupt type will obviously be critical of an article that aims to improve on the facility!
The "Interrupt Thunking" article probably should have made clear the environment in which its use was anticipated. It is applicable to the event-driven program where there is a single thread of execution, which is interrupted by various hardware devices for example, serial port, parallel port, timer, etc. In such an environment an interrupt handler must always completely restore the state of the CPU after completion, to the state that existed immediately prior to the interrupt. Here the user's program is directly handling the interrupt, not the underlying operating system. This is the case for many MS-DOS applications. In what follows I will refer to this case as the "single-threaded environment."
The environment of Mr. Johnson's letter is that of a Multitasking Kernel, or Real Time Executive, where the kernel will be handling the interrupt completely and performing all the hardware level I/O. After the device that caused the interrupt has been serviced, it is likely that an entirely different process context will be restored to the CPU. Obviously to think of simply restoring the pre-interrupt register state to the CPU after the interrupt service routine has completed in this environment is inappropriate. Hence void interrupt functions are not relevant. When using a Multitasking Kernel, I agree with Mr. Johnson's ideas.
Apart from being against the use of the void interrupt type, Mr. Johnson's letter is an exaggeration and misrepresentation of what the "Interrupt Thunking" article proposes.
Now to refer again to the single-threaded environment. The use of void interrupt functions is convenient, readable, and portable. The compiler ensures that the processor's state is saved and restored.
Contrast this with coding in assembly language the risk of simple program errors is much greater, it is far less readable, and less portable. All of this for that last ounce of execution speed! Nonetheless, there are times when speed is really the issue and assembly coding is necessary. An indirect advantage of using assembly language is that it might protect the programmer from inadvertently calling a non-reentrant function (such a function that uses one or more static variables) in an interrupt subroutine. However C programmers writing interrupt code should be aware of such traps.
Using void interrupt functions is not an open invitation to call the functions of the C library in interrupt functions. The Interrupt Thunking article never implies this. Given that interrupt handlers generally service hardware devices, it is difficult to see why an interrupt function would want to use anything more than the inport and outport functions, or equivalent macros.
A single hardware interrupt can have multiple causes on one device, for example on the PC UART chip (8250 or later), there are four different causes of an interrupt. The interrupt identification register is read to find the relevant cause. In order to keep readability and structure, it might well be necessary to have a number of subfunctions that the main interrupt handler calls for these different causes. This is why the article refers to an "interrupt function and its function hierarchy" (page 41). Once again this does not imply the calling of C library functions, only good program structure, made possible by having sufficient stack space.
Object-oriented design is applicable to all levels of a program, regardless of whether an OOP language is used or not. Unfortunately there appears to be a tendency among programmers to think of objects as high-level entities only. Consequently when the "Interrupt Thunking" article starts referring to accessing objects from interrupt handlers, warning bells ring! Interrupt functions do access program data objects. These might be flags, queues, or tables. Interrupt thunking is intended to provide elegant access to these low-level objects.
The article was not intended to describe the possible pitfalls of coding interrupt functions in C. Programmers writing interrupt functions should be aware of the dangers, for example that of calling non-reentrant functions (as in MS-DOS), and of race conditions in accessing variables used in the interrupt function. I admit that the small timer program example given in the article does have possible race conditions, however it still demonstrates the main point of the article.
Within these restrictions, interrupt thunking saves having static or global variables being accessed from interrupt functions, and thereby improves on the void interrupt function type. It is applicable where the void interrupt type is useable.
D.G.Taylor
Editor,
Thanks to Jack Hawes for raising an interesting question. ("The Problem with const Data Members," CUJ, March 1997.) I think, though, that there is a little confusion reflected in his example.
While a person's date of birth is logically and self-evidently a constant entity, a person is not the same thing as a person-record and the same constancy cannot be applied to the record's DOB member. A person-record's DOB member may quite legitimately contain an "unknown" value which may subsequently become known, or a data-entry person may have misentered a date of birth which would imply the need to be able to correct it. Thus no data of this sort, whatever its real-world constancy, is a good candidate for a const data member. C++ constancy is not the same as its real-world counterpart. To paraphrase what one of your columnists said a few months back, "const is a contract with the compiler not to change an object" during the life of the const object or the scope of the const function.
So what would be a good candidate for a const data member? I would say that such an animal should only be used if 1) its initial value must be known at the time of its construction and 2) it cannot ever change afterward. This sort of constancy can probably be assumed only as part of an automated process. One example might be a sort of id-numbering scheme where the id number would be initialized from some static value which is auto-incremented every time a new object is created. Even in this case the assignment operator problems would exist, but it would make sense if id numbers were unique and not to be affected by assignment or copying. For example,
class X { X() : id( ++nextid ) {} X(int a, char b) : id( ++nextid ), A(a), B(b) {} X( const X& right ) : id( ++nextid ), A(right.A), B(right.B) {} X& operator=( const X& right ) { // id unaffected A=right.A; B=right.B; } const int id; static int nextid; // must be initialized somewhere. int A; char B; }Other than this, I can't really imagine much use for non-static const data members and don't think I have ever used one. Const functions and parameters, where the constancy is of a shorter duration, are much more useful, as Mr. Hawes implies.
Steve Cohen
RDI Software Technologies
stevecoh@mcs.comI basically agree. Every time I've tried to use a const member object, it has caused some kind of trouble sooner or later. pjp
Editor,
I just got my April CUJ. Maybe I missed something while napping. When and where did the practice of creating header files without extensions start? Is this part of MS trying to wean us from the archaic DOS filename format?
Charles Hotchkiss
Senior Engineer
Wiltron Co.You can't blame Microsoft this time. Committees X3J16 (now J16) and ISO WG21 get the credit/blame. The Library Subcommittee decided several years ago to abandon the long-standing .h suffix convention for library headers. I don't like the decision, but I fought it and lost (a familiar outcome). You'd be amazed at how many bugs there are in DOS-based tools when it comes to handling filenames with no suffix. But it has become the wave of the present. pjp
Dear Mr. Plauger:
I am a fairly new C++ programmer, and I am a new reader of your magazine. I was wondering why there is no power operator in the C++ language. I use the function pow() from the math library extensively to convert numbers from one base to another, and it would be great to see a power operator in the C++ language for a couple of reasons:
1) Eliminating the function call and replacing it with a fully optimized compiler operator would be great for speed and eliminate the need to link in the math library in most of my programs.
2) It seems like every other "high level" language supports such an operator. Aesthetically, it seems like C++ is "incomplete" without one.
I have checked a C++ user's group FAQ page and have discovered that it would not be feasible to overload the ^ operator because of precedence issues. Also, if the ^ were overloaded, it would still be a function call, and thus the optimizations that would be most beneficial would not be realized. In addition, adding a ** operator would not be feasible because the compiler could not distinguish between this and the pointer dereferencing operator, from what I understand.
I would like to find out if the C++ Standards Committee is planning on adding a power operator to C++. If not, how does one go about proposing one? I would like to propose that the ^^ be added to the language as the power operator, with a precedence just above the * and / operators. My (limited) understanding of compiler writing is that this would not be very difficult to implement, and of course it would not affect any of the existing code base.
Is it possible for everyday "real-world" programmers such as myself to submit such a proposal to the committee? How is this done?
Thanks for the great magazine, and thank you for your time!
Andrew Foulks
Software DeveloperDennis Ritchie decided early in the lifetime of C not to include an exponentiation operator, even though he copied all the other operators from Fortran, and then some. My guess is that he felt the complexity of the operation should not masquerade behind operator notation. I vaguely recall some discussion about adding the operator to C++, but in the end the C++ Standards Committees chose not to do so. I personally don't think that the function call overhead is an issue, given the time required to perform all but the very simplest exponentiation. Note also, by the way, that both C and C++ compilers are permitted to optimize calls to pow as they see fit, since the name is reserved as an external symbol to the Standard Library. pjp
Dear PJ,
Just a quick note. I am writing you (like half the rest of the planet) because your name is on the STL headers which ship with Microsoft C/C++ 5.0. I have two questions:
When I run the compiler at warning level 4, I get a lot of warnings which I thought Microsoft would have caught in their testing. They are all harmless, and generally of the type 'unreferenced formal parameter' of signed/unsigned mismatch on a comparison. I have tweaked my copies of the headers to get rid of them, but it puzzles me that Microsoft would let this through. Your code is of course OK. (Did you run lint on them?)
Secondly, I am new to STL and now a huge fan. However, I found examining the header files a great deal of unnecessary effort. Why are they formatted so tightly without any comments? Granted once you get a feel for them it is not so bad, but when the debugger steps through an alien STL file, it is hard to tell what is going on. It takes a while to trace back through all the typedefs and macros. As a casual user I would have found it easier if each function had a header and explanation, plus some of the on-line documentation. Have you considered condensing some of your standard C++ articles and putting them in the actual headers themselves?
John O'Halloran
The code is pretty thoroughly exercised on several compilers. Each compiler seems to have its peculiar list of things to complain about, however. The choice of headers is dictated by the draft C++ Standard. The tight presentation is an artifact of the way I format code for books. Thanks for writing. pjp
Dear CUJ:
I use opaque pointers (pointers to incomplete types) to provide compilation firewalls in my C++ code. For instance, I might have a class that maintains a list of pointers to objects of some other class without ever actually using any of those pointers. In this case I simply make a forward declaration for the contained class and use pointers to it without ever manipulating it, as in the following example:
class Anonymous; class AnonymousArray { private: Anonymous *mList[100]; bool Set ( int Index, Anonymous *Thing){ if(Index>0 && Index<100){ mList[Index] = Thing; return true; } } Anonymous *Get( int Index ){ if(Index>0 && Index<100) return mList[Index]; } };This will compile. And, in a system where Anonymous is defined, it will run. However, if I write similar code using an STL list or map, my compiler complains bitterly about my use of an incomplete type. I'm using MSVC++, which contains P.J. Plauger's implementation of STL. This implementation seems to be defining, as part of the default allocation class, an operation that will call the destructor for my Anonymous class. I can't think of any reason why a container would need to destruct objects pointed to by the objects that it contains. Is there something that I don't understand about STL containers?
Sincerely,
Phil Goodwin
It's possible that the compiler is actually complaining about something slightly different. Try adding the function
void _Destroy(Anonymous **p) {}The default allocator in STL will attempt to destroy your pointer to incomplete type, and the compiler might not like being asked to do so, particularly as part of a template expansion. I've received other reports of template-generated destructor problems. (Coulda sworn that this machinery once worked right in VC++, but I could be wrong.) In any event, you can fix the problem if my diagnosis is correct by supplying an explicit specialization of the offending template that avoids the offending destructor.
Good luck,
pjp