Dear Robert,I subscribe to many journals but this is one I read first. It is in-depth and correct, an excellent journal.
However, I must correct your response to Mr. Hagan's letter in the Jan. 1990 issue (page 138): Pascal, like C, stores multiply indexed arrays in row major order just as C does and unlike Pascal Fortran. (I'm doing it, too!)
Nonetheless, thank you for excellence!
Sincerely,
David B. Teague
PO Box 2866
Cullowhee, NC 28723Seems like I should re-run the letter from a few issues back that chastised me for shooting off my mouth without researching the subject. I used to know these things when I used to teach them. Apparently "used to" is the significant fact. It's great that CUJ can be useful in spite of my flaws. rlw
Robert,
I've been with the magazine since the June '85 CUG issue. I like the mag and the changes you have made to it.
I'd like to use the magazine to inquire if anyone has any code out there to do Printed Circuit Board design and routine. I'd be happy to start out with something imperfect so don't worry about the completeness of any submissions.
Thanks a lot.
Richard Howells
Farmarsh House, Cotmarsh,
Broad Town, Wiltshire
SN4 7RA, United KingdomThanks for the kind words. Here's your request. rlw
Dear Mr. Ward,
A couple of comments on the 3/90 issue (Volume 8, Number 3)...
Page 24 column 1 paragraph beginning "It was implied earlier...", sentence beginning "Also, an increasing": This sentence by itself is correct, but its implication in context is not. A function can be coded inline only when the compiler knows of what the function's code actually consists. That is why one must put inline function definitions in the .H files, rather than the .C files, of modules. Clearly, use of inline functions cannot apply to so-called "jump tables" (which really should be referred to as "call tables" since functions are being called), since the compiler is explicitly ignorant as to which function is being invoked by a given call and thus cannot include the target function's call directly in the code it is compiling.
In other words, while you can do:
inline int max(int a, int b) { return a > b ? a : b; { void main() } int a, b, c; /* Get values for a and b from somewhere. */ c = max(a,b); }and expect the call to max by main to be compiled inline, you could not expect to even try something like:
int anyfunc(int (*fn) (),int a,int b); void main() { int a, b, c; /* Get values for a and b from somewhere. */ c = anyfunc(max,a,b); /* Same max as above. */ } int anyfunc(int (*fn) (),int a,int b) { return (*fn) (a,b); }and have the reference via *fn to max in anyfunc to be made inline, since it is not clear when compiling anyfunc what function it will end up calling. (Yes, a global optimizer might, but wouldn't for the more real example of the call tables described in the article.)In trying to understand what is or is not possible, I often resort to thinking in terms of generic assembly code. So, while I can easily envision how the former example is compiled into assembly code, the latter is inconceivable in the general case (more than one caller of anyfunc.)
One possible optimization might be for the compiler to detect, based on global optimization, what possible functions can be accessed through, in this case, fn, compile them all inline, provide a true jump-table access path to them within anyfunc, and convert into jump-table addresses all references to those functions that end up going to anyfunc. Pretty sophisticated stuff.
In other words, for the most part, when you use call tables, you are going to pay for those calls. But aside from machines like Prime 50 series, where normal procedure calls are somewhat expensive (and feature-laden to an extent not needed by C), speed should not be a problem. Even on Primes, one could teach the compiler about short-calls, but in the call-table setup, this requires some fancy footwork, also. (I haven't worked on Primes in years; they probably already have this kind of optimization in their C compilers.)
By the way, I've adopted a similar approach quite often in the past, and am currently using one in a large project I'm working on. I'm writing a front end where the choice of lexer is not known until run time. I could have the syntactic analysis call the lexer via a variable rather than use a switch statement, but in fact I invert the whole approach I have the lexer call the syntactic analysis functions by delivering tokens to them. They return the next syntactic analysis function to call. The maintenance so far seems to be easier. What really drove this design was the need for ambiguity resolution by trying to parse one kind of statement, abandoning it due to an error, and then trying another, and so on until one succeeds. For this, it seems easier to have the lexer drive the parser than the other way around. (Though I'm fairly confident it could have been done the other way around!)
Curiously, C seems to offer no way to cleanly define a type that identifies a function whose return value is another function of that type. For example: given 26 functions named A through Z, accessed via a variable whose type is intended to be FN, so the caller just declares FN foo;, initializes to, say, A via foo=A;, then repeatedly calls via foo=(*foo)(args);, there seems to be no valid way of actually constructing the typedef for FN. I want to say "typedef FN (*FN)();", but naturally this upsets a C compiler that doesn't know what FN is. (I'm trying to tell it, "FN is a type of pointer to function returning type FN", but C requires that I specify the return type before I specify the type I'm defining. When they are the same type, you get this problem, and here is the only legitimate need to do so that I know of offhand.)
Instead, I build up the definition by doing typedef void *FN_sigh_; typedef FN_sigh_ (*FN_sigh_2_)(); typedef FN_sigh_2_ (*FN)();, which seems to be enough levels to keep what should be unnecessary casting to a minimum. But one still must say, for example "return (FN) K;" within one of the state functions when it returns the next state. (I'm doing all this under THINK C 4.0 on my Macintosh your mileage may vary.)
Page 46 column 2 paragraph beginning "Suppose Yact[T] is derived...": My understanding of inheritance is that, if B inherits (is derived from) A, then the statement "B is an A" should be true. My understanding of sailing is that a sloop is a yacht, but a yacht is not necessarily a sloop. Thus, when picking these arbitrary names, I would have chosen Sloop to be inherited (derived from) Yacht instead of the other way around.
If memory serves, the inheritance tree for yachts should look something like:
yacht is a ship; //a yacht has masts for sails sloop is a yacht; // a sloop has one mast [bimast] is a yacht; // a bimast, a made-up name, has two masts schooner is a yacht or a [bimast]; //a schooner has three masts, or two masts with the aft mast // not shorter than the forward mast ketch is a [bimast]; // the aft mast, or mizzen, of a ketch is //forward of the rudder // yawl is a [bimast]; the mizzen is behind th erudder (not the // same as the helm)I guess this makes a schooner a good argument for multiple inheritance!Oh, and thanks for the great magazine. By the way, my favorite article was probably the belief maintenance system one. It talked about something I don't think much about but am curious about, and it gave me enough information to experiment if I wish, but I don't have to just to find it interesting.
Sincerely,
James Craig Burley
4 Mountain Gate Rd.
Ashland, MA 01721-2326My graduate degree allegedly includes an emphasis in AI, but I hadn't seen the Dempster-Shafer approach earlier, so I too found that article very interesting. I'm not certain I understand your comments on the inheritance issue; here in Kansas yachts and sloops all look alike. rlw
Dear C Users Journal,
Thank you for many interesting articles relating to C programming. People seem to use a variety of naming conventions for function and variable names. I believe one convention stands out above all the rest for ease of maintenance and readability: The Stanford Naming Convention.
Because case is significant in C programs, the Stanford Convention suggests using it to:
1. Differentiate between global and local identifiers
2. Separate parts of words
Global identifiers are initially capitalized, and local identifiers (including qualified identifiers, such as field labels and structure tags) are initially uncapitalized.
Identifiers made up from several words use capitalization to indicate where each word begins, for example BlockSize would be a global identifier, and numberColumns a local identifier. See the example below.
Example Function with Stanford Naming Convention: /*******************************/ /* UPDATE SYSTEM DATA BASE / /*******************************/ unsigned int UpdateDataBase(typeOf- DataBase) int typeOfDataBase; { extern int SystemTime; extern unsigned int ModifyDataBase(); extern unsigned int RejectDataBase(); int dataBaseStatus; if( SystemTime > 1100 ) dataBaseStatus = ModifyDataBase ( typeOfDataBase ); else dataBaseStatus = RejectDataBase ( typeOfDataBase ); return( dataBaseStatus ); {Sincerely,William T. Hedrickson
Global Wulfsberg Systems
6400 Wilkinsion Dr. Prescott, AZ 86301Dear Editor,
The assistance received from The C Users Journal in my efforts to achieve greater understanding of and greater competence in C programming language is appreciated. With regard to my current projects, I am wondering if either you or any of your readers might know of an inexpensive source of annotated source code for any of the following three types of programs:
Also, do you know the address of A.I. Architects software company? Article writers frequently refer to their products, but I have not been able to find an advertisement from this company. Many thanks.
- elementary spreadsheet
- elementary sideways printing routine
- elementary database interpreter
Yours truly,
Lee Shackelford
520 Messina Drive
Sacramento, CA 95819-2520I don't know how elementary it is, but CUG volume 238 contains rotate.c, a demonstration program which prints text sideways using Conrad Kwok's shareware graphics library.
You can contact A.I. Architects at: One Intercontinental Way, Peabody, MA 01960. Phone (508) 535-7510 or FAX (508) 535-7512.
Tell 'em we sent you! rlw
Dear Robert Ward,
This letter regarding what I believe are coding errors published in the July 1989 article on "Accessing the MS-DOS Master Environment" by Scott Ladd may be inappropriate since someone may have hitherto brought these errors to light since I missed, much to my chagrin, the issues of The C Users Journal between the August 1989 one and the February 1990 one due to letting my subscription lapse. If, however, no one else has reported these errors, would you be so kind as to do so in a forthcoming issue of the magazine for the benefit of other programmers who have adapted Mr. Ladd's routines for their own use and, especially, in conjunction with Leor Zolman's article in the February 1990 issue of "Tools For MS-DOS Directory Navigation."
The first error is a quite serious one leading to the dreaded Memory Allocation Error, cannot load COMMAND.COM etc. In the M_DELENV routine after the loop that looks for the matching environment string and the test for whether it was found or not, the next while loop, after the
/* otherwise, copy the reaminder of the environment over name */comment, should be within an if statement:
if (*e1) { while (!((*e1 == NUL) && (*e1 + 1) == NUL))) { *e2 = *e1; e2++; e1++ } }The reason for this is that the end of the environment may have already been reached and e1 be pointing to the second of the double NUL bytes rather than another environment string.The second error is less serious and occurs in the M_PUTENV routine where the remaining space in the environment is calculated, after the comment
/* get the amount of remaining space */as
l = env_len - l - 1;I have it instead as
l = env_len - l - 3;The double NUL bytes at the end of the environment don't seem to have been taken into account in the first calculation. If I am wrong about this I will be glad to be corrected. I say less serious because the error will only cause havoc when the environment is nearly filled up and the environment string to be added is 1 or 2 bytes too long. The first error causes problems whenever the deleted environment string is the last one in the environment chain and double NULs occur again somewhere off in the wild blue yonder (past the end of the environment memory block).Finally I would like to say that the article was extremely clear and well-written despite the coding flaws. If others would just slow down and explain things clearly to the reader and not assume that the reader knows everything that he or she is writing about immediately, the quality of the articles in The C Users Journal would vastly improve.
Yours truly,
Edward Diener
I'm glad you found Scott's work useful. I appreciate your taking the time to pass along these corrections. As for explaining everything, we always try for that ... the problem is striking a balance between offending some by assuming they know too little and losing others by assuming they know too much. rlw