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).
Reader's Feedback
For me, using or not using goto is not a religious, but a technical issue: programs with goto are ugly, inelegant, harder to maintain. Most of all, it is unnecessary. For all these reasons, I don't like your code for truncating files.If I were to code a similar thing from scratch, I would likely do it very differently, but I decided to cope with the harder task of modifying your code to make it gotoless (Listing 1) . In doing so, I kept, almost untouched, its original structure. This is like playing tennis with handcuffs.
In my opinion, it is not possible to make good code from a bad start, but these changes do prove contrary to what you stated "that gotos do not avoid a lot of nested logic." On the contrary, gotoless code may do it.
On the other hand, I do not agree that return counts as using goto. The problem with goto is that we don't know where it goes to. But a return always has a perfectly known destination: the calling function. What a difference!
I am confident that even working under the strait jacket of your code, my version is more readable, more elegant, neater. Granted, most of this is subjective stuff.
As to your answer concerning static and automatic pointers, I cannot agree they are sort of equivalent, as your own example itself clearly shows. They are used for different reasons, with different results.
As if it were necessary, let's make it clearer with the example in Listing 2.
I don't doubt you know this perfectly well, but to me your answer sounded misleading.
Fernando Cabral
BrazilThank you for your submission. I wish you had rewritten it from scratch. Seeing how other people create solutions to problems is always a good learning experience. I will agree with you that your conversion of my code to one not using gotos makes it slightly unreadable.
The problem with gotos is that they tend to be abused. A goto should not be used to create a loop there are plenty of standard constructs for doing that. I believe gotos should usually be reserved for getting out of code in which an error condition occurred. If error testing was eliminated, my original code would probably not have a goto in it. Your do_copy function could look like Listing 3 without error checking.
If you prefer returns` to goto end, you could take my original code and substitute the gotos for returns. Or if you reverse the logic of the tests in the original, you can eliminate some of the gotos from my original code. I have a tendency to think of things in a negative manner, that is, "if this is the case, then it's no good." The inverse of this logic thinks of things in the positive manner, that is, "this must be the case if it is to be good." It just delays the error messages to the else part.
In a sense, whether you code it with nested logic or with gotos depends on your sense of completeness. A goto end ensures that nothing else will be done in that module. With nested logic, you need to check all the remaining statements to be sure that nothing else occurs for a particular condition.
I think both ways are valid after all some people drive on the left and others on the right. As long as you are consistent in your approach, then I don't believe it matters too much.
As you mentioned, code style is subjective. If code never needed to be maintained (sort of write-once and use forever), then how it is written would not be an issue. To keep maintenance simpler, I tend to write in a consistent style, whether the problem is simple or complex.
Let me list a few features that I use fairly consistently in all my code. First, I form names of variables and functions from full words, except for common abbreviations. Some programmers still tend to stick with the 8 character K&R limitation, but that makes C read like FORTRAN.
Second, I use a number of temporary variables simply to provide an easy way to display their values. For example, you used
if (write(temp_file, buffer, to_write) < to_write)where I used
write_length = write(file_out, buffer, length_to_write) if (write_length != length_to_write)If I am either using a debugger or printfs to trace through the code, then it is easy to output the return value of the write function. Now you could write this as
if ((write_length = write(file_out, buffer, length_to_write)) != length_to_write)I shy away from such combinations since they tend to run over multiple lines (especially with long names).Third, I use a single return statement to simplify tracing the return value. If you are setting either breakpoints or using printfs to output the return value of a function from inside the function, then you need to set multiple breakpoints or to insert multiple printfs. Multiple returns cause problems when tracing return values. Similarly, I use only a single variable as the return value, rather than an expression. As an example of this, let's take a simple function
simple_function(x, y) { /* Lots of code */ return 3 * x; /* Lots more code */ return 4 * x; /* Lots more code */ }If you were using a debugger within this function and wanted to trace just the return value, then you need to set two or more breakpoints at each return statement. When the breakpoint occurred, you would need to output the value of an expression. Alternatively, the code could read
simple_function(x, y) { int return_value; /* Lots of code */ return_value = 3 * x; goto end; /* Lots more code */ return_value = 4 * x; goto end; /* Lots more code */ end: return return_value; }In this case, only a single breakpoint needs to be set and a single variable output. Since my functions which use gotos have a label immediately before the return statement (at the end of the function), where the goto goes is consistent.Fourth, I try to never use constants in the executable portion of the code. To shorten the original example, I did not use #defines as I normally do. Statements as ret = -4; should really be replaced by
#define ERROR_WRITING_TEMP_FILE -4 ... ret = ERROR_WRITING_TEMP_FILE;These are all subjective features. A die-hard C programmer might feel that functions should look like
truncate_to_zero( char *path) { int fd; return fd = open(path, O_WRONLY | O_TRUNC, 0666) >= 0 ? close(fd), OK : NOT_OK; }I hope my answer to the static/automatic pointer problem wasn't too misleading. Your listing does point out a distinct difference between the two. In the context in which I showed it, there is no substantial difference in usage. The point I was trying to make is that the array of characters is set aside in memory at a constant address in either case. Whether that constant address is used to initialize the pointer once or multiple times depends on whether the pointer is static or dynamic.For example, consider Listing 4. Both functions will pass back the same string. The difference is that p_auto will be initialized every time and p_static will only be initialized once.
Code Generators
I am writing in regards to "Using Code Generators For Creating C Code," in the March, 1992 issue. I have been using PRO-C. It seems to work really well for generating C code. I have written a full application in about one hour. That's complete with designing a database, indexes, screens, and menus. I think that's neat. I am still rather an infant when it comes to C. So when generated code compiles the first time with no need for modification that's alright with me (especially for $99). You also can hook up to the data structures of your choice. The people at PRO-C LIMITED say the next version is even going to be better. It may possibly contain support for the SoftC Database Library. I have used PRO-C with the runtime version of Btrieve supplied and with my PARADOX Engine. PRO-C is worth trying out. Also their technical support is great. Their number is (519)571-0802 FAX (519)571-1504. They also are on Compuserve (GO PCVENB).Gene Lockwood
Maspeth, NYThanks for your information. Our readers may want to give this a try. (KP)
I was reading your column in the March 1992 issue about code generators. At the end of your response, you asked to be informed of other code generators that were available.
I wanted to let you know about a tool called Knowledge Shop. It is a development tool and code generator for C programmers wishing to incorporate rule-based systems into their code. Although excessive hype caused rule-based systems to get off to a slow start, their use in a wide variety of applications is increasing.
If there was ever an ideal use for a code generator, it would be in building rule-based systems. Hand-coding tens, hundreds, or even thousands of interacting rules starts out difficult and rapidly approaches impossible. Decision System Software started life as a custom programming company that builds rule-based systems into applications. Knowledge Shop evolved as a result of our increasing frustration trying to do ever more demanding work with inadequate tools.
Enjoy your column. Keep up the good work.
Craig T. Paulson
Cromwell, CTThanks for your letter. Rule-based systems have good potential for automatic code generation. Decision System Software is at 160 West Street; Cromwell, CT 06416; (203) 632-7570. (KP)
I'd like to add some comments (gripes, even) in answer to Steve Kohut's question on C code generators (March 1992 issue). I've worked with a half dozen or so C code generators and I've found many that hung me out to dry. I'll just mention two of this ilk, and I'll disguise the brand names to protect the guilty.
The first generator, DataAcq, specialized in real-time data acquisition, analysis, and display. DataAcq provided its own editor with a "control-panel" interface that relieved me of the need to riffle through endless function-library manuals. It also prevented me from writing bad function calls. Besides, the little switches and knobs and stuff were cute.
When I looked at DataAcq's code, I found that all those cute little switch selections had yielded unreadable function calls. Instead of clear, direct code like
setXYGraphColors( White, Red, Red, Yellow, Green),DataAcq wrote
setXYGraphColors (15,4,4,7,3).Had DataAcq's designers never heard of enumerated types or constants?Worse yet, I couldn't modify existing code with the control panels, because DataAcq couldn't read what it wrote. That made two of us! I had to abandon DataAcq because it was clearly useless throughout most of the software life-cycle. I was back to riffling through endless function-library manuals.
The second generator, Facelt, specialized in text-based user interfaces. Facelt included a full-screen editor with which I could design an entire interface screen. I could lay out text, background colors and data fields. I could specify data field types, name variables, and define valid ranges. When I finished editing I could simply tell Facelt to go, and presto, I had C code.
Alas, Facelt's designers also never heard of enumerated types or constants. Or information hiding! Facelt wrote one monstrously large C source file that contained parameter-less function after parameter-less function.
How did Facelt stand up to the software life-cycle test? I found that if I didn't customize the code in any way, I could go back to the screen editor and make the changes there. But Facelt didn't provide enough features and it had limited feature-extension capabilities. I was compelled to modify Facelt's code, and I could never again use Facelt's editor. I was back to writing the code myself.
I, too, heartily recommend superb code generators like lex and yacc. They are compact, powerful, and work as advertised. For code generators like DataAcq and Facelt, caveat tweakor!
William J. Hoyt, Jr.
Middletown, CTThanks for the feedback. I've found the same problems with a couple of screen generators that I've used. lex and yacc are definitely great generators for language and command parsing. In a sense, they fall in the lines of the product mentioned in the previous letter. (KP)
Dynamic Link Libraries
Phar Lap has DLLs under its 286|extender. Right now we're using Novell's OS/2 Btrieve DLL under regular old MSDOS.Robert A. Rose
Fort Lauderdale, FLThanks for the info. (KP)
Program Error
This is in regards to a question by James A. Gant in the March 1992 issue. Listing 4 of his C program has the following line:
if (fp = fopen("screen.pic", "wb") == NULL)The equality test operator has a higher precedence than the assignment. Therefore the variable fp will be assigned the result of the test and not the address of the FCB that fopen returns. Depending on the compiler and C libraries, he may get different results from the file generators.P.S. I just had exactly the same bug in one of my programs.
Kevin Towers
New Westminster, BCYou have a sharp eye. This statement will be incorrect in ANSI standard compilers (as well as any K&R one). The parenthesis must have gotten dropped somewhere in the transcription process. (KP)