Rex Jaeschke is an independent computer consultant, author and seminar leader. He participates in both ANSI and ISO C Standards meetings and is the editor of The Journal of C Language Translation, a quarterly publication aimed at implementors of C language translation tools. Readers are encouraged to submit column topics and suggestions to Rex at 2051 Swans Neck Way, Reston, VA 22091 or via UUCP at uunet!aussie!rex or aussie! rex@uunet.uu.net.
This month I'll present more puzzles, though there will be a slight twist. These puzzles either contain syntax errors or produce unexpected output and are derived from problems my introductory C seminar attendees often encounter. This should be a lesson in "quality of implementation." That is, just how good are the error messages produced by a compiler, and what warning messages, if any, does it produce? An ANSI-conformant compiler is not required to warn about anything. It is permitted to say "Syntax error" once at the end of a compilation that detected 100 errors. It's not even required to indicate the offending line numbers. In short, the quality of such messages is a marketplace and vendor-conscience issue.
As before, I've included the answers, but you should try to debug them on your own first. You might also want to see what messages your compiler produces. In some of the puzzles and solutions, I have included the actual messages from one or more DOS-based compilers. PC-Lint, a static analysis tool from Gimpel Software, produced the other messages. Since the primary job of lint-like tools is to go over your code with a fine tooth comb, it is not surprising that PC-Lint gave even more help than did almost all of my compilers. (While the exact message text has been retained, a common line number prefix has been added to each to make them easier to read and compare.)
The moral of this exercise is that if you can at all afford it, buy more than one compiler or buy a lint-like tool in addition to your compiler. If you can't or won't do either of these, at least buy a compiler that has many lint-like features built in. (That's why I selected Turbo C when it was first introduced.) If nothing else, switch on the maximum warning level of the one compiler you do have.
To say that you cannot afford another compiler or lint-like tool but you can afford to waste an open-ended number of man-hours is silly. By far, the biggest cost of software development is the human resources. In comparison, the cost of development tools is negligible, at least in PC environments and even in non-trivial projects on larger systems. If you nickel and dime, you'll get a proportional result.
As a consultant, I initially evaluate a potential client's commitment level to solving his problem in a reasonable fashion by examining his investment in tools and training.
The Puzzles
1. Incompatible return type.
/* 1*/ struct tag { /* 2*/ int i; /* 3*/ double d; /* 4*/ } /* 6*/ f(struct tag *pst) /* 7*/ { /* 8*/ return (pst->i); /* 9*/ } line 8 - Expressions are not assignment compatible line 8 - 'return' : incompatible types2. I don't know what I want but I don't like what I see.
/* 1*/ #include <stdio.h> /* 3*/ void f() /* 4*/ { /* 5*/ int c; /* 7*/ While ((c = getchar()) != EOF) /* 8*/ putchar(c); /* 9*/ } line 8 - Syntax error, expected: ; <operator> [ ( . -> , line 8 - Expecting ; but found fputc3. Some comments on tokens.(See Listing 1. )
line 15 - Syntax error, expected: ; <operator> [ ( . -> , line 15 - missing ; before identifier printf line 15 - Statement missing ; in function main4. Hey, where's my output?
/* 1*/ #include <stdio.h> /* 3*/ main() /* 4*/ { /* 5*/ switch(6) { /* 7*/ case 2: printf("case 2\n"); /* 8*/ break; /* 9*/ case 4: printf("case 2\n"); /*10*/ break; /*11*/ default :printf ("default\n"); /*12*/ break; /*13*/ } /*14*/ }No compilation errors or warnings were produced but there was not any output.
The Solutions
1. The compiler sees that I am returning an int type expression. However, for some reason it is expecting something else. But what? The function is intended to have a return type of int. I did not explicitly declare this, but that is the default. I'll explicitly declare it and see what happens.
/* 6*/ int f(struct tag *pst) /* 7*/ { /* 8*/ return (pst->i); /* 9*/ } line 6 - Too many types in declaration line 6 - type following struct is illegalWell, what the compiler is expecting as a return type is still not exactly obvious, but the structure type appears to be somehow involved. If the implicit return isn't int, what is it? Here's PC-Lint's hint.
line 8 - Type mismatch (return) (struct = int)Now I know that a structure type is actually expected as the return type. However, it is still not obvious why since that was not my intent.Quite a few years ago, the ability to pass and return structures and unions to/from functions by value was added to the language. Consider the following:
struct tag { int i; double d; }; struct tag f(struct tag *);Here, f is a function taking one argument and returning a structure of type struct tag by value. In the rare cases you see such return types declared, they would be declared as shown, with the structure template separate from the prototype. However, both can be combined. For example:
struct tag { int i; double d; } f(struct tag *);This is also permitted when defining a function, and that is exactly what the compiler saw with the original source. If you look carefully, you will notice that the semicolon was omitted at the end of the structure template definition. As such, it was seen not only as the template definition but also the return type of the function.As you might expect from this discussion, templates can also be defined within a function's argument list. For example:
void g(struct tag {int i; double d;} *);From a stylistic viewpoint, I urge you to define such templates separately so they can easily be placed in headers.2. Well, the first message isn't much help. The second is similar, but refers to fputc instead of putchar. With this compiler, putchar apparently is defined as a function-like macro that expands to a call to fputc. With maximum warning levels activated, the following more useful messages were produced:
line 7 - Warning! No prototype found for While line 7 - Warning: Function While not declaredNow the problem is obvious. The keyword while was spelled with a capital W. Since all keywords must be spelled in lowercase, the construct While (...) looks like a function call (to an undeclared function returning type int). The token putchar makes no sense at that location, so it is flagged.I have seen this problem arise more often with Pascal programmers, who seem to prefer capitalizing leading characters in identifiers. It also happens with If. This is a growing style with X-Windows, MS-Windows and OS/2 programmers.
3. Similar to the previous problem, the compiler objects to the token printf, not because there's something wrong with it specifically, but because such a token cannot be used in this context. The problem is, therefore, "Just what context are we in?"
Perhaps increasing the warning level will help.
line 13 -Warning: Comment within comment line 13 -Warning! Nested comment found in comment started on line 10Sure enough, there's something suspicious, and the second message is even more helpful than the first. A nested comment is a comment inside another comment. This might seem to be necessary in the following context:
/* Disable the following piece of code /* ... */ printf("Debug data is ..."}; End disabled section */Neither K&R nor ANSI C permits the nesting of comments, although some compilers provide this as an extension.So what's all this got to do with our problem? We aren't trying to nest comments! Read the second warning message again carefully. It indicates that a comment was begun on line 10. Do you see it? If not, go into an infinite loop until you do. While the expression i/*pi states exactly what you want, the value of i divided by the value of the int pointed to by pi, what is actually seen is the token i followed by the start of a comment. This comment ends at the next */seen, at the end of line 13. And since another/* is seen along the way, the compiler warns that the start of another comment was seen inside the first comment.
This kind of division expression is not particularly unusual, so how should we write it to get what we really want? Putting white space between the / and * would make it look silly, and some unsuspecting maintenance programmer may well remove it. The best approach is probably to use parentheses as in i/(*p), and to also add a comment to the effect "Don't you even think about removing these seemingly redundant parentheses." (Certainly, indirection has higher precedence than division, so the grouping parentheses are redundant from that perspective.)
Regarding the nested comment issue, "If I really want to disable code containing comments, how do I do it?" The solution has always been to use the preprocessor in a non-intuitive manner. That is, to conditionally compile code such that it is never accepted. For example:
#if 0 /* Disable the following piece of code */ printf("Debug data is ..."); /* ... */ #endif/* End disabled section */It's maximally portable and strange enough to get your attention. It's also easy to locate #if 0 with your editor to reactivate it in the future.4. I admit this is a contrived puzzle I have never actually seen anyone accidentally trip over it. However, it's still interesting. Since the switch controlling expression matches neither of the cases, you would expect control to pass to the default label. Clearly this did not happen since no output resulted.
According to PC-Lint though, there is something suspicious.
line 13 - switch statement has no default line 14 - default (line 11) not referencedOn the one hand, there is no default case, but on the other there is a default label that's not referenced. Isn't that contradictory? Well, we sometimes read what we want to read, not what it says. Note that the unreferenced label is spelled "default" not "default." The letter "L" was actually typed as the digit "1" making the label an unused user-defined label (suitable for use with goto only). Of course, being able to goto a label in the middle of a switch construct is very bad style, but style is the business of the programmer, not the language definition.