Kenneth Pugh, a principal in Pugh-Killeen Associates, teaches C language courses for corporations. He is the author of C Language for Programmers and All On C, and is a member on the ANSI C committee. He also does custom C programming for communications, graphics, and image databases. His address is 4201 University Dr., Suite 102, Durham, NC 27707. You may fax questions for Ken to (919) 493-4390. When you hear the answering message, press the * button on your telephone. Ken also receives email at kpugh@dukemvs.ac.duke.edu (Internet) or dukeac!kpugh (UUCP).
Q
I'm writing a program using Microsoft C v5.1 and am trying to give the user the ability to press a key to cancel the printing of a file.
From your March 1990 CUJ Q/A section I got started. Using different variations I came up with the enclosed program source code (see Listing 1) . I'm using the setjmp() and longjmp() functions to return the desired location when the user presses a key to cancel the printing.
The problem I'm having is when the longjmp() is performed and I return to the code following the setjmp(). The local variables are bad and apparently the return address is bad. It appears that the local variables have not been restored correctly because I print the addresses (and contents) of the variables before and after the longjmp() is performed and the segment portion of the address has changed.
This problem occurs only when I'm reading and writing the file to the printer. If I perform an infinite loop with no disk or printer I/O being performed, the longjmp() recovers okay. Also, I've written a critical error handler (24H) that performs a successful longjmp() when the user chooses to "abort" from a "printer not ready" condition.
I've tried the different levels of Microsoft I/O functions (stream, low level, and even _dos functions) with the same result. What is happening in the I/O functions that causes this to happen, and how can I get around it?
David Schaefers
Oak Brook, ILA
I can make a guess as to why it doesn't work. A call to a handler for the critical error (0X24) is a nested function call. The program calls DOS, which calls the printer routines, which calls the critical error handler, if necessary. In this instance, the interrupt is only an indirect method for accessing a function.
The keyboard interrupt (09H) is not a nested call. It occurs asynchronously and is generated by hardware. The standard states that longjmp should work in the context of an interrupt and signal, but this appears to apply only to those interrupts that are handled by the signal function.
As an alternative, you could simply set up an external variable whose value would be set by the interrupt routine. Suppose it was declared as:
int abort_print;Your main program would not make any setjmp references. The print loop might look like:
abort_print = 0; while(0 < (read_cnt = read(read_handle, buff, 4096))) { write(prn_handle, buff, read_cnt); if (abort_print) break; }The interrupt function would be modified to look like:
if(kbhit()) /* key in buffer */ { _dos_setvect(0x09, old_int09); abort_print = 1; }You should set your own function for the critical error interrupt, since it is possible to get a "Printer off-line or out of paper" error. An abort response to that message should reset the keyboard interrupt function. (KP)Q
I am delighted to read the April 1990 issue (among a lot of others) about the short discussion on "indentation and readability" to find that this is also an issue that people consider.
I am a beginning C programmer and quickly discovered that it is often difficult to find the matching braces pair. Actually, with whichever ways you presented in the discussion, it is still difficult when the open and close braces are far apart, and even worse, when they are on different columns in an article or even different pages. The attached examples should illustrate my point. If there are nested switches and while loops, good luck.
Now when I write programs, I would add a short comment next to the close brace to indicate where its other half is, such as shown in Listing 2.
I don't know whether this will become trivial when programming experience grows. But for now, I need several color pens when reading longer programs written by others in order to join the braces pairs.
Another suggestion for improving readability of long program files with lots of functions: Please arrange the order of the functions in some systematic way, e.g. alphabetically. Or at least, arrange the function prototypes in order of their appearance in the program.
Kim Tsang
Hong KongA
I fully agree with your method of commenting closing braces. I always follow it myself for long loops or lots of embedded braces. For the listings in this magazine, I tend to leave them off because of the narrow column width. I don't see the listings until they come out in typeset form. For a few of them, I wish I had comments as you describe.
I confess I am less prone to alphabetize functions in a listing. I tend to group them together based on related operations. It would be a simple matter to write a program that created a index for the functions or use a cross-reference program to yield an index. (KP)
Q
I'm trying to compile a program (Listing 3) using the Turbo C+ + compiler and I get the following error:
Turbo C++ Version 1.00 Copyright (c) 1990 Borland International q_sort. c: Error q_sort.c 12: Type mismatch in parameter '_fcmp' in call to 'qsort' in function main *** 1 errors in Compile ***This error is removed if the stdlib.h qsort() prototype is changed for the compare function from const void * to unsigned char **.Does that mean I have to make changes to stdlib every time I try to compile a program using qsort that calls a compare function with different parameter types? What if I have a program with qsort called twice each time with a different type of compare function. Technical support at Borland had no answer. Can you help?
Firdaus Irani
Chestnut Hill, MAA
I've tried out your problem with Microsoft C and it gives a similar warning message. By switching the prototype to read
int comp(const void *, const void *);the message went away. Even with the comp function in the same file with unsigned char **, there was no disagreement between the prototype and the function header.I tried switching the parameter in the qsort prototype to read
_fcmp(void *, void *)but the same warning message appeared. The standard implies that the parameters should match, but it appears that they do not. This may be because it is a nested function prototype. (KP)[Looks like a bug in Turbo C++ to me. (PJP)]
Reader's Replies
Global Declarations
I would like to make some additional comments concerning your reply to Richard J. Wissbaum's "Array vs. Pointer" question in the August 1990 CUJ. Several years ago, a programmer I was working with on a large C application made a mistake identical to that made by Mr. Wissbaum. It took the two of us nearly a week to track down the mistake, and it has made a lasting impact on my coding style. I now declare all global variables in a common header shared by all modules that will use them. Each variable is declared exactly once. Listing 4 shows how I would have coded Mr. Wissbaum's example.Not only does this approach centralize all global variables but it forces the definition and declarations to be identical. I have also initialized the variable "date" to demonstrate the use of the INIT() macro. In cases where the INIT() macro falls short (such as initializing a structure) I code:
struct S_MyStruct { int i; long 1; char *p; }; CLASS struct S_MyStruct MyStruct #ifdef DRIVER = {0, 21L, NULL}; #endif ;I'm not real happy about the dangling semicolon but I have not yet found a better way to code it.In your reply to J. A. Jaffe (same issue) you state that a single #define is preferable to two defines for the same value (one numeric, one quoted). I contend that multiple declarations of global variables is just as bad, if not worse.
Bob Withers
Allen, TexasA
Your point is valid. I tend to prefer using a separate header file for the extern version of the declaration and including that in the source where the variables are actually declared and initialized. My real preference is not to use global variables at all, except for static externals. That eliminates having to worry about double declarations. (KP)
offsetof Macro
I was browsing your Q/A in The C Users Journal (June '90, p. 71) when I spotted your mention of the new ANSI macro offsetof. You say that the purpose of this macro is to "eliminate having to declare a variable simply so that these offsets can be calculated." I came across a programming trick years ago that can simulate the offsetof macro with no price to pay in performance or obfuscation.The code in Listing 5 shows how anyone can simulate offsetof and use it.
Notice that no variables need to be declared to get the offset of a field in the record. The preprocessor transforms the offsetof into a number. These offsets may need to be stored in an array if you want to use them in a loop on the fields, of course.
This offsetof function relies on a simple and ingenious idea that I cannot take credit for (I don't know who first thought of it):
The offset of a field in a structure is equal to the address of that field if the structure begins at address 0. Moreover, by taking 0 and typecasting it to be a pointer to that structure, you can take the address of an element and get the offset.
I'm not certain it will run on all compilers, but I have used it on a number of them including Microsoft's C compiler for DOS. I hope you find this little trick amusing.
Aninda Roy
Schaumburg, IlANSI simply took the equivalent of your definition and made it a standard macro. (KP)
[This trick doesn't have to work right on all compilers. Some implementations may do something different. (PJP)]
Basic Numeric Values
Thank you very much for your response to my question about UNIX and MS-DOS file I/O.The CUG library should now have my source code for packing and unpacking BASIC numeric values. I've been doing this for years. Reading the data is fine, but updating the information can also prove very valuable. I work with a system on which I must maintain data with both C and BASIC programs.
The code includes functions to perform BASIC crd(), crs(), cvi(), mkd(), mks(), and mki() functions. I have used these on both MS-DOS and XENIX 3.4b.
Vern Martin
Alliance, Ohio
Reader's Requests
As a reader of The C Users Journal, I thoroughly enjoy your column, and find your answers quite useful. On the job I have programmed in C on a variety of systems. But at home, currently the only computer I have is a rather obscure system made by Yamaha (the music company, not the motorcycle) that is based on MSX style of computer (a hardware standard attempted by Microsoft, I believe, that caught on in Europe, but bombed here). It's a CX5M computer. My question: do you know where I could find a C compiler for this system? Since it is designed around a standard system (MSX), I assume someone, somewhere may have written one, but I am unable to find it. I contacted Carolyn Engineering in Springfield, VA. who still sells the CX5M and CX5MII computers, and no-one there knew where I could find one.I appreciate any assistance you can lend and appreciate your taking the time to respond.
Jay Orr
Vienna, ILAnyone know about this one? (KP)
.EXE file segments
I am trying to store the configuration data for an .EXE within the .EXE itself. I've allocated static data space in my program for the config data and have also placed a unique key within that structure. When the .EXE is loaded into memory it begins by opening and reading itself in binary mode searching the location in the data segment that holds the unique key. Once found it can read and write to that data area. My problem comes into play when I try to index into the .EXE to the beginning of the data segment (in order to cut down search time when seeking where the static structure is located). Starting from the beginning of the .EXE is far too slow. Does the header information in an .EXE give you the ability to index directly to the beginning of the data segment and if so how?Ken Yerves
Jacksonville, FLUnless you included the symbol table in the load file (via debugging options), there does not appear to be anything in the .EXE file that can directly help you. You can get relocation information, and even where the load module starts in the file, but there does not appear to be any information on the beginning of the data segment. Perhaps one of our readers has other ideas. (KP)