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 was a member on 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).
Q
I am not an experienced programmer, and only a fledgling C programmer, so forgive me if I'm overlooking the obvious.
I am attempting to program an engineering application. I have discovered that most of the tools I need to make this project feasible are readily obtainable (for a price) through add-on libraries. For one aspect of the project, however I have not discovered any commercially available libraries. That aspect is in providing a "C language interface" to the user, or preferably, providing warm (or dynamic) linking capabilities to user written object files. Ideally, my application should serve as a quasi 'platform,' for which other users write their own applications. Those applications should be easily linked (and unlinked perhaps like overlays) to the platform at run time.
Borland's C++ supports DLLs, but as nearly as I can tell, for Microsoft Windows only. I am writing for DOS.
In order of preference, my choices for resolving this are:
1) Buy a library (provided it's less than a few hundred dollars) which provides DLL capabilities under DOS,
2) Buy a book which describes how to do it, or
3) Read about it in a series in CUJ (unless you can just answer it in this column).
David Qualls
Choctaw, OKA
That's an interesting question and very similar to one that I am working on for a client. It all depends on how tightly coupled this "C interface" needs to be with your currently executing program. One solution is as follows:
1. Buy a commercial C interpreter (Instant-C, C-terp, etc.).
2. exec (or spawn) out to this interpreter. It allows for quick compiles and executes. You may have to roll out your current program in order to allow enough room for the interpreter to work.
3. If only data is exchanged between your program and the client's program, provide the names of the files you will be writing out to the user (or reading in from the user) and the format that you will be using. If you have an elaborate file structure, provide a library of routines that can access the files (e.g., the same ones you probably already use in your program).
Most interpreters can link in with an object library, so you do not need to provide source for these routines.
4. If you want dynamic linking between your program and the user's program, you can create a TSR that the user's program would execute when it needed a function from your program. Your TSR would respond by executing the appropriate function. For complex applications OS/2 or UNIX permit an easier method of remote procedure calling between tasks or processes.
Q
I'm a student working in a software engineering lab at Ecole Polytechnique de Montreal. I need to find the best C++ compiler for the HP9000s300. I already tried to install GCC on the machine. It seems to work but it looks like I can't use the GNU debugger GDB. Do you know if G++ works on HP or of any other C++ compiler that would fit ? Thank you very much for your help.
Jean-Sebastien Neveu.
Montreal, CanadaA
Anybody know about this one? You might try Comeau Computing's C++. It creates C source as its output.
Q
I am a subscriber to The C Users Journal and I must say it is a very informative magazine. I have been working with C for the past year and I find it to be a very elegant language to use. It gives you the syntax of a high-level language and the power of a low-level language.
Since I began learning C, I have encountered many obstacles. The greatest obstacle was getting books which can adequately teach a novice programmer how to use the language, its weaknesses and strengths, and how to implement the famous algorithms, for example, sorting of files in memory. I have, on many occasions, had to import books from the C Users Book Store. I would not have come this far without their help.
Now that I have learned the basics and some advanced programming using C, I want to start writing programs for my own use and for sale. I find this difficult because I do not know where to get books on what I want to do. For example, where would I find a book that teaches you how to build a text editor from scratch without using external library functions in C?
I am appealing to you to send me a list of books, their prices and from where I can obtain the books. If it is from a publisher, I would be grateful if you included their address and the price of the book. The topics I am interested in are listed below.
1. Text editor I would like to learn how to write a text editor from the beginning and build up to more advanced functions like word wrapping, cutting and pasting text, etc.
2. Terminate and stay resident programming I would like to learn how these programs work (TSRs) and how one can go about writing his own TSR.
3. Graphics programming I would like to learn how to write graphic programs similar to Dr. Halo, and animation graphics. I would prefer books that use functions available in Turbo C or Quick C.
4. Windows programming I want to write programs that will have windows in them. I would prefer a book that includes, for instance, programming stacked windows.
Your help would be greatly appreciated. I hope to hear from you soon.
Moses Mwarari Maina
Nairobi, KenyaA
That's a tall order. The C Users' bookstore carries most of the available books on programming in C. In the areas that you mentioned, my favorite is Rochkind's Advanced C Programming for Displays. TSRs are not well covered in the books that I am familiar with. Past issues of The C Users Journal and the C Gazette cover some of these topics (TSRs especially). Perhaps our readers might have their own suggestions.
Q
My problems with C lie chiefly in the area of effective organization. I spend more time reorganizing my code than I do developing it. Can you recommend any books that would help me with this aspect of the language? I'd like to be better organized from the start.
Art Shipman
Westbrookville, NYA
I have a few tips in my book All on C: Chapter 15, "Design and Coding," and Chapter 16, "Packages of Functions." There are many good general design books, but they tend to cover large scale (multi-programmer) projects. Perhaps our readers might have a suggestion.
Q
I am experiencing difficulties in printing to my Epson FX-80 printer directly from a C program. I copied and used the code provided in your "Questions and Answers" article (June 1990, page 72, Listing 1) in an attempt to access the printer. The program output apparently resides inside a buffer, because after running the program, exiting to DOS, and typing print autoexec.bat, the program output is printed on a separate line, before the autoexec.bat file.
I have tried replacing the LST in that listing with PRN, and stdprn with similar results. I have not tried printing to other than the Epson FX-80 printer. I am using Turbo C 2.0, and Borland C++ 2.0, Compaq DOS 3.31, a Compaq SLT 286 computer with 640 K ram.
I suspect the solution to this problem is "child's play" for you, but I have not been able to find it in any of my C literature.
An additional question for you which may be more difficult: Is graphic output to a printer accomplished in a manner similar to text output?
Listing 1 shows a modified copy of the listing from the June 1990 isssue.
Gary F. Rynearson
Harrisburg, PAA
Put a '\n' at the end of the print and it will come out properly. The line is stuck in the buffer on your printer. To increase the speed of printing many printers print every other line backwards. This avoids having to move the printer carriage back to the left side for the next line (ala a typewriter). So the line is buffered, until a new-line is read, which tells it that the current line is finished. It then prints that line, clears its buffer and waits for the next line.
Note also that simply closing the printer does not generate a line-feed or form-feed ('\f'). You need to explicitly write one to the file.
Graphics printing on the surface is not different than text printing. You may need to open the file in binary (b) mode to avoid spurious CR/LF conversions. You first send the control sequence to set the printer into graphics mode, as specified in the manual. Then the bytes that are sent represent the dots on a single column (i.e., about 1/8 of a character wide). Creating the output is much more tedious than actually sending the bytes themselves.
Readers' Responses
Debugging
In reference to Mark Petrovic's lock-up bug (see CUJ September 1990, page 111, and CUJ January 1991, page 98): I've experienced the same weird lock-up. Like Mr. Petrovic, I could turn the bug off or on by inserting or removing a printf command. Typically what happens is that the bug disappears when you add the line designed to inspect some variables. You say "Wow, it's fixed!", take out the printf, re-make and run, and the bug returns.I've also managed to toggle this bug simply by adding or removing an unused variable which receives the return value from a function call. Changes elsewhere in the program, however, will make these bug-toggle solutions fail. But now I may have an answer to this problem.
The conio.h file supplied with Turbo C Version 1.5 itself contains a bug. The #ifdef/#endif wrapper, which should provide protection from multiple inclusions of that header file, doesn't do the job. The #endif, which should be at the end of the file, is up at the top of the file with the #ifdef and #define. I haven't had any recurrences of the bug Mr. Petrovic describes since I moved that #endif to the end of conio.h. What compiler does Mr. Petrovic use?
Art Shipman
Westbrookville, NYI read the letter and your answer about "Debugging Problems," page 124, The C Users Journal, June 1991. I have had similar frustrating experiences but I have never been able to pin the trouble specifically on the compiler. Yes, I have done some strange things that the compiler did not question; but I could fix them in a logical way and remove the problem.
I have found that whenever inserting or removing code causes the rest of the program to change its behavior, it is as you suggested: writing in the wrong place.
I am including a few code segments which have caused me heartburn in the past. These are all bugs that we learned to avoid in the early days of learning C and are relatively easy to find in a short program. When the program is hundreds of lines long, the mind seems to give up and overlook the obvious.
char *a; int i; ... for(i=0;i<10;i++) *(a+i):' '; /* fill array with spaces */ /* Error: unassigned pointer */ *(a+i)='\0; /* Terminate string with null */Okay, so we'll fix this problem by adding:
a = malloc(10*sizeof(char));and the problem seems to have gone away because now the pointer does not point to an unknown area. Wrong. Sooner or hundreds of lines later, we'll add code and the program will fail again. Why? Because buried in the code is the *(a+i): '\0' statement that writes one character past the end of the assigned array. The array was malloced without space being allocated for the null terminator. Fix: add space for the null terminator.
a = malloc((10+1)*sizeof(char));A similar problem exists with the following code:
char a[10]; int i; ... for(i =0;i<10;i++) a[i]:' '; /* Fill array with spaces */ a[i]='\0'; /* ERROR: Put terminating Null past the end of the array */And a similar problem exists in a different guise with the <= limit as shown in Listing 2. I searched 36 hours straight for that equal sign.Listing 3 shows another variation of the same problem. The = sign is no longer the problem but pre-incrementing the variable produces the same bug.
Of course there are tools to help find these kinds of problems. The compiler usually has a switch which activates a null-pointer checking, a runtime option. Lint by Gimpel Software, MemCheck by StratosWare, and BOUNDS-CHECKER by Nu-Mega Technologies are several of the many good tools to help the tired mind search for the dumb errors. LINT finds most of the errors and omissions in address assignments. MemCheck helps find invalid memory usage.
And, for 386/486 machines, BOUNDS-CHECKER will flag attempts to access memory that the program does not own. There is a caveat on the use of BOUNDS-CHECKER however. It will flag gross memory address violations but, depending on the overhead bytes that the compiler adds when memory is malloced, it generally won't flag the one-byte-past-the-end-of-the-array bug. This is because the malloc() "owns" overhead bytes (sometimes only 4 and sometimes more than 100) past the end of the array.
Ken, in summary, I believe it is dangerous to "fix" a program by moving code around or adding code. The "fix" is symptomatic of the types of problems you mentioned in your response to the previous letters examples of which I have shown above. The error still remains and will "byte" you when you can least afford it probably during the demo to your boss, customer, or (worse yet) your spouse. "Honey, come, let me show you this great program I've been working on (and ignoring you) for the last 6 months."
I enjoy your column. Keep up the good work.
George W. Smith, Jr.
Chatham, NJLet me propose a slight alteration of your code in the style I usually use to try avoiding some of the problems you have described. I will either code your loops as
char *a; int i; a = malloc(SIZE_A*sizeof(char)); for(i = 0; i < SIZE_A - 1; i++) a[i] = ' '; a[SIZE_A-1]='\0;or simply as
char a[SIZE_A]; int i; for(i = 0; i < SIZE_A - 1; i++) a[i] = ' '; a[SIZE_A-1]='\0;In either case, the use of the SIZE_A without the 1 clues me into the potential of an overrun on the index. Alternatively you could code it as follows:
char *a; int i; a = malloc((SIZE_A + 1)*sizeof(char)); for(i = 0; i < SIZE_A ; i++) a[i] = ' '; a[SIZE_A]='\0;or
char a[SIZE_A+1]; int i; for(i = 0; i < SIZE_A; i++) a[i] = ' '; a[SIZE_A]='\0;In this case, the array will be one bigger than actually required. This allows for the one-greater-than slippage that may occur frequently, as you suggest.The statement *(a+i)='\0' on the malloced array may fail during execution or the program may exit with the dreaded message Memory Allocation Error System Halted. You have performed one of the worst MS-DOS C programming errors writing past the end of allocated memory. You stomped on the allocation control blocks that precede or succeed the allocated memory.
Your <= problem is definitely more insidious. I see a variation of it in the classes that I teach. It runs something like
long datapointer[SIZE_DATA_POINTER]; int ticks; for (ticks = 0; ticks <= SIZE_DATAPOINTER; ticks++) { datapointer[ticks] = 0; }Thanks for your comments. (KP)In Art Shipman's comments in the June 1991 issue of CUJ, I get the impression that he feels that the addition of the variable to accept the return value solved his problem. Unfortunately this is almost certainly not true.
1) If his problem appeared to go away after adding a variable to capture the return value, the problem is not solved, only moved somewhere else. While it is usually good practice to utilize return variables, the act of simply not reading the variable cannot of itself cause a failure. The C standard does not require a return value be read.
2) If a bug is altered in performance by simply placing a printf in the code, it is very possible that the problem is nowhere near the printf statement in question (often not even in the same function) but somewhere else in the code, typically an invalid pointer, array overrun, or stack corruption.
Unfortunately, these problems can be very difficult to track. If the bug causes a hard crash, possibly the code is being corrupted. If one has a source debugger (such as Turbo Debugger) it is sometimes helpful to put a global watch on a few bytes of code around the printf statement and watch for overwrites. Also if possible, display the stack while single stepping through that area of code, watching for any suspicious changes.
The following is the case history of a bug that was inactive until an innocent change brought it out of the woodwork and may be helpful to other readers:
A function containing a few automatic variables and a small automatic character array had worked reliably for a long time under Power C. When recompiled under Turbo C it compiled without error but crashed whenever the function was called. As it turned out, due to a programming error, the automatic array was being written past the end by a couple of bytes. Power C had placed the variables at an address above the array on the stack, so the overwrite affected the variables (which didn't happen to be used after the overwrite.) Turbo C placed the array above the variables and consequently the overwrite trashed the return address.
Jay Holovacs
Warren, NJThank you for your suggestion. I agree with you that addition and deletion of automatic variables should not affect the program. Checking the values variables declared before and after an automatic array would help to trace an overrun problem. (KP)
Remove Comments On remove
Ken, you've been sleeping while writing your column again! For the first question, you answered that the problem had to do with float to double parameter conversion. That's not quite true the problem is with compilers that "adjust" float parameter declarations to double (much the same way that array parameter declarations are "adjusted" to pointers).Many consider this to be a horribly unintuitive practice (although K&R does sanction it) and there are compilers that do "the right thing," namely convert the passed double into a local float (note this conversion is actually a noop if you're lucky enough to have a floating point format where a float is just the first half of the bytes in a double true for IBM and DEC floating point, but not for IEEE). ANSI C no longer allows this "adjustment," but requires the conversion; see 3.7.1 Function Definitions Semantics.
The second question dealt with compiler warnings about a function named remove. You answered that this was a problem with QuickC's header files not being ANSI compliant. However, remove() is an ANSI standard function that belongs in stdio.h! The writer should rename his function. I know you know better, so I'm attributing these slips to brain fade it happens even to the best of us!
Larry Jones
Milford, OHMy slip on the first question may revolve simply around semantics and my way of stating the answer. In the K&R version of C, all floating point parameters were passed as doubles. A conversion (promotion) took place, even if the parameter was declared as a float. This was not an "adjustment" (i.e., a cast without an actual operation required). This was a real time-consuming conversion. For example, with K&R,
float float_variable; function(float_variable); function (float_variable_received) float float_variable_received; { /* Body */ }float_variable is converted to a double. float_variable_received will be declared just as if you had used double.With ANSI C, float_variable is still converted to a double if a prototype is not in scope. However, with the ANSI version,
int function(float float_variable_received); float float_variable; function(float_variable); int function(float float variable received){/* Body */
}
the float_variable will not be converted to a double. But inside an expression involving floats such as
a = float_variable + float_variable;it may be converted to a double. This is under the "as if" rule. As long as the results come out the same, it does not matter how the operation is performed.On the second, I am mea culpa. I must have been really asleep. After all, it's on page 517 of All on C. I have no idea what I was thinking about at the time maybe the unlink() function in UNIX. And you are absolutely correct in your fix. The ANSI standard requires a perfectly compliant program to not redefine any of the standard functions.
Thanks to Stephen Clamage at Tau-Metric on this same matter. (KP)