When we last left our hero, we were looking at
ten ways to get screwed by the "C" programming language. Today's entry is
Phantom returned values. The example is:
Suppose you write this
int foo (a) {
if (a) {
return 1;
}
} /* sometimes no value is returned */
Generally speaking, C compilers, and C runtimes either can't or don't
tell you there is anything wrong. What actually happens depends on the
particular C compiler and what trash happened to be left lying around
wherever the caller is going to look for the returned value. Depending
on how unlucky you are, the program may even appear to work for a while.
Now, imagine the havoc that can ensue if "foo" was thought to return a pointer!
Rubbish. The bit about random stuff getting returned is true enough. But C compilers can mostly tell that sometimes a value isn't returned from a function that is declared returning a value. While
gcc does not report any warning by default, the -Wall option yields the following warning:
return.c: In function 'foo':
return.c:5: warning: control reaches end of non-void function
That is, if a function is declared void, it doesn't have to return a value, and therefore it can drop off the end. In C, one can't return a value by dropping off the end. One must use a
return statement; So, for example
int bar(a) {
return 1;
while (1) {
;
}
}
This does not produce a warning. If you remove the
return statement, it still does not produce a warning, because the compiler knows that the
while statement is an infinite loop. If you remove the
while statement but leave the
return, the compiler again knows that the end of the function is not reached. The return statement itself causes a return, and returns a value. If you change the return statement to just
return;, the compiler warns this way:
return.c: In function 'bar':
return.c:2: warning: 'return' with no value, in function returning non-void
All this to say that compilation with
-Wall can catch errors that might otherwise consume your time. If you fail to use the option, shame on you. If you use the option and ignore the results, shame on you. In the early days, these warnings were available from a utility called
lint, which knew the language but did not actually produce an executable. Presumably, the thought was that burdening the compiler with warning reporting was too much for each iteration. But the result was that people didn't check for errors as often.
Yet, there is a common case where you have a function that is declared to return a value, but you often know that the return value isn't used. Worse, you sometimes know that this function won't need it's arguments, but it is declared with them anyway.
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("Hello, World.\n");
}
produces these warnings
hello.c:4: warning: return type defaults to 'int'
hello.c: In function 'main':
hello.c:6: warning: control reaches end of non-void function
Now,
main returns a value to the environment. That is, the parent process, often the shell, gets the return value, and may use it for loop control, error reporting or whatever. But sometimes you know that it won't be used. And the same is true for the arguments. The above program does not need to know the command line arguments. If you write
main without a return value, the compiler will complain at you.
hello.c:2: warning: return type of 'main' is not 'int'
What to do? Best advice is to return a value anyway.
In the
original top ten, the
return statement was written this way:
return(1);
While this works, and does what is expected, the parenthesis are not needed. The
return statement is a statement, not a function, and does not need parenthesis around the argument(s). I like a style where functions have the parenthesis right next to their name(s),
return statements have none, and loops such as
for (...) and
while (...) have a space between the keyword and the parenthesis. Visually, the reader can learn to recognize functions as functions without thinking about them too much. This convention and is never enforced by compilers, so consistency, the hobgoblin of little minds, must be enforced by self discipline. As a matter of style, i'd like to think that i'm not nearly arrogant enough to think i've got no hobgoblins. And besides, it's not for me to judge if my mind is little or not. It's a job for God, if she cares.
I am, however, qualified to judge C compilers. The bar for C compilers is
gcc. If the compiler you supply isn't as good as
gcc in some way, you have little excuse. The
gcc compiler is available for free in source form. You can download the source, find out how it behaves, examine the source code, and you can update your compiler with that knowledge. If your product isn't as good as one available for free, eventually your very smart customers will use the superior free one. That doesn't mean, for example, that
gcc won't optimize some corner case slightly better than yours once in a while.
gcc is, after all, very good. The bar is quite high for C compilers.