infobahn said:
I don't.
You are wrong to infer that. I do in fact understand boolean variables.
I also understand that in almost all cases, foo() does not return a
boolean variable, but an int.
When a logical expression is evaluated in C, it is evaluated as an integer
expression and it determines if the integer is zero or non-zero. Sounds
boolean doesn't it? Two states: either zero or non-zero.
If foo() is truly boolean in nature,
then I am perfectly prepared to write if(foo(bar)) rather than
if(foo(bar) != 0)
Nothing in C is truly boolean. There is no C type for boolean. There are
only integers that are evaluated in logical expressions to be either zero or
non-zero. While that sounds like boolean values, you are technically correct
in claiming that is not truly boolean, but as far as the C language is
concerned the logical alternatives are zero or non-zero. Notice also that if
you do a logical not (!) on any non-zero value you will get zero. Perform
the same operation on zero and you get a non-zero value.
But the fact that you find the form "annoying" suggests that you need to
relax a little.
Absolutely. And a boolean variable can only answer one of those
questions, which is why so few of my functions return boolean
variables.
I should say that none of your functions return boolean variables since
there is no such thing as a boolean variable in C. Unless of course you
create one through typedef, but what are you actually doing if you do that?
You are defining an enumerated data type that can have two values which are
either zero or some non-zero value. Are you beginning to get the feeling
that C considers integers to be boolean values that are of two kinds, zero
and non-zero when expressed in a logical context?
No, it should be obvious to any C programmer worth their pay that
the result of strcmp is not boolean, but relational (negative,
zero, or positive).
The C language treats the result as a boolean value in a logical context,
it's either zero or non-zero.
We do, however, care whether the result is negative, zero, or positive.
Why? Normally, I only care if the strings are the same or not. The distance
between each other is not something I can normally do anything about.
I, however, do not encourage early exit from a function. In all too
many cases, this makes the control flow harder for a maintenance
programmer to follow. Maintenance programmers are often under a lot
of pressure to fix a problem quickly, in code they don't know very
well. Anything we can do to help them out is a bonus, and writing
your functions to have all the structure of spaghetti bolognaise is
not helping anyone.
If you think early exits from functions results in spaghetti, then you don't
know what spaghetti is. From an earlier post:
Consider the (pseudo) code examples below:
int func(arglist)
{
(declarations)
int retval = BADSTATUSVALUE;
void *pointer;
if(condition1)
{
pointer = malloc(SOMESIZE);
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
if(condition2)
{
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
if(condition3)
{
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
if(condition4)
{
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
while(condition5)
{
long_line_that_ should_
be_broken_up_because_it_is_so_long_and_indented;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
retval = GOODSTATUSVALUE;
}
} /* condition4 */
} /* condition3 */
} /* condition2 */
} /* condition1*/
free(pointer);
return retval;
}
I'm sure most of us have seen code similar to the above. Now consider:
int func(arglist)
{
(declarations)
void *pointer;
if(!condition1)
return BADSTATUSVALUE:
pointer = malloc(SOMESIZE);
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
if(!condition2)
{
free(pointer);
return BADSTATUSVALUE;
}
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
if(!condition3)
{
free(pointer);
return BADSTATUSVALUE;
}
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
if(!condition4)
{
free(pointer);
return BADSTATUSVALUE;
}
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
while(condition5)
{
long_code_line_that_doesn't_have_to_be_broken;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
codeline;
}
free(pointer);
return GOODSTATUSVALUE;
}
Now then, this second example is far easier to deal with. The extreme
indentation has been eliminated, the distant brace matching has been
eliminated and the retval variable has been optimized away. This results in
several less things to keep track of. With the less indentation, fewer long
lines need to be broken up.
Also consider how the first example reads in the sense of ordinary language:
if condition1 keep going if condition2 keep going if condition3 keep going
etc.
as opposed to:
if not condition1 stop if not condition2 stop if not condition3 stop etc.
This is more consistent with a language that refers to traffic signals as
stoplights not golights.
But nobody has to maintain the code in K&R's book. Also, whilst I
greatly admire both K, R, and their book, the day I use K&R as a
style guide is the day I grow an extra arm.
I'm not recommending K&R as a style guide, in fact there are aspects of
their style I don't care for. I was, instead, intending to point out how
they use the return values of functions such as strcmp as if they were
boolean types and also how they use them to prompt early returns from
functions. This is why C works the way it does as far as logical expressions
are concerned. The intent of the designers of the language becomes clear. It
intends for logical expressions to transcend the limits of pure boolean
algebra.
I am fortunate enough not to have got used to early exit from functions.
I am very happy for you if you are used to it, but I have developed my
own style based on my own reasoning and logic, and I'm quite happy to
use that style.
You might want to consider if your own reasoning and logic isn't a little
too much your own.
Your style for you; my style for me.
Oh, I do that already, and have done for many years.
There, I must agree with you. What makes this worse is when foo() is not
a boolean function, and yet its result is still compared against a
"boolean" symbol such as TRUE or FALSE.
Oh, I see. You're complaining against code structure.
I'm complaining against awkward code structure.
I prefer:
if(foo(bar))
{
rc = baz();
}
else
{
rc = NONZEROSTATUSVALUE;
}
return rc;
Do you have a problem with the nesting depth here?
What you wrote should be written as:
return foo(bar) ? baz() : NONZEROSTATUSVALUE;
Why should anyone have to keep track of rc when it is not needed? Why should
anyone have to follow several lines of flow of control when none are needed?
In the code you provided, it is too easy for that harried maintenance
programmer to just stick in trial bits of code inside each of the brace
pairs until they stumble upon something that takes care of the problem,
nevermind that it can create another problem or obscure the purpose of the
code. In the case I provide, the maintenance programmer should have no
trouble understanding the single line of code and if they don't (they
shouldn't be coders for a living), they had better figure it out before they
attempt any modifications. Requiring maintenance programmers to actually
know what they are doing is not a bad thing.
However assuming that you could have provided a more complicated example, I
would have expected it to be similar to the form in the re-posted (pseudo)
code example 1. Having given that example, you should have no trouble seeing
how early exits from functons improve the readability of the code.
Yes, that's what I thought you meant before.
This code, whilst correct, will have the newbies (and some older hands)
reaching for their K&Rs. Better: c = (a == b);
If it gets someone reaching for their K&Rs then so much the better. They
should be up to speed on the language. Those of us who are shouldn't have to
dumb down our code for those who aren't up to speed. In fact anyone who had
to reach for their K&R to understand that piece of code falls into the
category I was complaining about before, those who don't understand the use
of boolean values in C. As far as the parentheses are concerned, I find it a
trivial issue. Either way should not be a problem for anyone. If adding the
parentheses helps you feel better, I don't have a problem with that. The one
place I do have a problem with extraneous parentheses is when they are used
for every return value as in:
return (somestatusvariable);
When I see that it makes return look like a function call, which it isn't.
Those who code that way don't seem to understand how return works. Having
said that I don't mind something like:
return ((some_expression) operator ((some_expression) operator
(some_expression)));
Where the parentheses can clarify the meaning. For a return value like that,
I'd probably also include a comment that explained the ramifications of the
expressions.
I would prefer c = (a != b);
While I don't see any real difference, I have to wonder why you'd find the
second way better. The way I put it preserves the sense of the original
code, but again I don't see one as having any value over the other.
I didn't find them particularly funny. Just silly.
Mmmm. Well, just pray you never have to maintain my code.
I probably never will, but I have had to fix code that follows your
conventions and typically the problem arose from a maintenance programmer
who followed the same conventions and couldn't be bothered to truly
understand what the code was doing. Chances are they were intimidated by the
bulk of the code. So they just stuck a few lines of code in, maybe some
initializers and by a hacking band-aid approach managed to fix the bug they
were assigned to fix. Nevermind that they made the code even worse, even
bulkier and even harder to maintain for the next person.
Not in my newsreader, they aren't.
In mine they were and I used spaces, not tabs.
Well, the compiler doesn't need them. The human sometimes finds them
useful, so I put them in /all/ the time.
This human finds them to clutter up the code. I've asked several coders who
adopt your method when braces are necessary and when they aren't. I've never
met one who knew. These are people who are being paid to write C code.
The purpose of the brace is to define the beginning and end of a compound
statement. When you put braces where they are not needed, I read it as the
compiler does as in "open brace: start compound statement" then you put a
single statement where a compound statement is expected. That would be
comparable to putting punctuation in English where none is expected.
(Charlie Brown writes a letter:
Dear, Grandma,
I, had a, nice day, at school, today,. We, studied punctuation, marks.
Today, we, learned about, the, comma,. )
For a start, they should be close enough together that you can
see which ones match which other ones.
Why should I have to see it if they aren't needed?
And even if they aren't,
if you line them up
{
like this
}
you don't have to worry about which ones match which, because it's
obvious.
I really don't have to worry about it if they aren't there.
And even if it weren't obvious, decent modern editors can
whizz you from { to } and back in the blinking of an eye.
If they aren't there at all, I don't have to ask my editor which ones match.