What do you think about the code?

R

Ronald Bruck

Joe Wright said:
Tom said:
you can write any code that uses a goto, without a goto.

Goto is disliked, as it leads to code that is harder to read. You
should try to eliminate goto's as it will make somebody(other then you)
able to edit your code.

Common newbieism. Try code like

----
if (func1() != OK) { goto ERR; }
stmt1;
if (func2() != OK) { goto ERR; }
stmt2;
if (func3() != OK) { goto ERR; }
stmt3;

return OK;
ERR:
/* err occured, deal with it */
---

versus the "smart" way

---
if (func1() != OK) { deal_with_error; }
stmt1;
if (func2() != OK) { deal_with_error; }
stmt2;
if (func3() != OK) { deal_with_error; }
stmt3;
----

Where deal_with_error could be say 10-25 lines of cleanup code
depending on the function. Now pretend you're writing a function that
has 40 blocks like that [say doing a lot of failable bignum math
calls].

goto is no more evil than the "do" keyword. I could just as easily
write incomprehensible switch statements too...

switch (mychar == 'Y') {
case 0: do_something();
default: or_not();
}

etc, etc, etc.

Anyone who claims "goto" is evil basically hasn't written a
sufficiently complicated enough program yet.

Tom
I don't claim goto evil except that it destroys program structure. I
have only ever used goto in little test programs to see how it works. I
am much more a fan of break and continue to defeat looping blocks.

I am reminded of an article I read at the height of the "GOTO"
controversy, way back in the 1970's: the author proposed
(tongue-in-cheek) a "COMEFROM" instruction. And he gave an example to
compute the factorial of 3.

The problem with the "COMEFROM" obstruction, of course, is that you had
to scan THE ENTIRE REST OF THE CODEBASE to see if you were supposed to
divert to that instruction. His sample program to compute 3! was the
worst spaghetti I ever saw.

Wikipedia has an interesting article on the concept. As they point
out, breakpoints are actually a form of comefrom.
 
R

Richard Riley

Richard Heathfield said:
Keith Thompson said:


Yes, syntactically of course it's clear. The issue I have with it is to do
with semantic clarity, not syntactic clarity. In simple cases, there
is

Semantically it is complicated how? I have never, in real life, seen
anything too complicated involving break that any half competent C
programmer couldnt decipher - especially with commented labels and a
debugged test run.
usually no difficulty understanding why a break is in place. But as the
codebase grows larger, as the comment density drops, as the all-important
deadlinedeadlineDEADLINE approaches, as the level of hacking around goes up
and the quality of the code goes down, the semantics of control structures
can start to get blurred and fuzzy. I've seen it happen, and I'm sure you
have too. (And there is never any budget for fixing it.) A more rigid

A break is no more complicated to debug or check than any complicated C
construct such as a for loop IMO. I have never had an issue with breaks
in code after checking for the obvious newbie/careless errors of fallthrough.

control structure syntax would force the programmer to think about what
he's doing. At present, there are far too many "light-bulb" moments, where
the programmer realises he can achieve his immediate objective with a very
small change. "Aha! I can frob the widgets at precisely the right time if I
just add in: if(v < repackagewidgets(G_KEYVAL, 38, "%%&!-3")) break; that
is a way-cool hack, saved me an hour" - and because it's "only a one-line
change" it "doesn't need commenting".

On the whole, C is still written by hackers, and it shows.

C is a hackers language and many constructs/semantics make it easy to
screw up : from casting to precedence to bracketing to ... etc

break is far from the biggest issue and certainly doesnt deserve
singling out IMO.
 
K

Keith Thompson

Ronald Bruck said:
I am reminded of an article I read at the height of the "GOTO"
controversy, way back in the 1970's: the author proposed
(tongue-in-cheek) a "COMEFROM" instruction. And he gave an example to
compute the factorial of 3.

INTERCAL actually implements this.
 
R

Richard Heathfield

Richard Riley said:

A break is no more complicated to debug or check than any complicated C
construct such as a for loop IMO. I have never had an issue with breaks
in code after checking for the obvious newbie/careless errors of
fallthrough.

Oh, but I'm not talking about break-out-of-a-case. I'm talking about
break-out-of-a-loop.


C is a hackers language

In many senses, yes. But the code doesn't /have/ to be unmaintainable.

and many constructs/semantics make it easy to
screw up : from casting to precedence to bracketing to ... etc
Indeed.

break is far from the biggest issue and certainly doesnt deserve
singling out IMO.

I didn't realise I was singling it out. You're right - there's plenty more
wrong with typical C programs than mere control flow issues. But that
doesn't mean control flow issues aren't a problem.
 
J

Jordan Abel

Ian Collins said:
What happens if you refactor out the inner loop?

Using break is almost a bad as goto at disrupting code structure. If
you want to exit a loop when a condition occurs, why not just test for it?

"just test for it" where? In the condition for the outer loop? How do
you exit a loop in the middle of its body, then? Or halfway through an
inner loop?
 
A

Andrew Poelstra

"just test for it" where? In the condition for the outer loop? How do
you exit a loop in the middle of its body, then? Or halfway through an
inner loop?

If you're designing your code cleanly, you shouldn't need to jump out of
a loop in the middle. I'm working on an XML parser right now without using
break; to escape from loops. It's a lot trickier to code, but the end result
is actually a fair bit easier to read, maintain, and extend.

That's just my opinion.
 
I

Ian Collins

Jordan said:
"just test for it" where? In the condition for the outer loop? How do
you exit a loop in the middle of its body, then? Or halfway through an
inner loop?

Where you test the other expressions.
 
G

goose

Ian said:
I haven't, if you read what I posted, you'll see I agreed.

Just making sure :)
Within a fixed scope, but you still identify the error handler (the
label) at the point of error, where throwing an exception does not.

Thats right; However I remain to be convinced that this is such
a bad thing. For a function of, say, ten lines (with the error-handling
code) I'd not hesitate to use a goto *when the error handler is
in the same scope*. If exceptions are available, I'd rather throw
an exception but if exceptions are *not* there for me to use
I'd chuck in a goto rather than have the logic code do
unneeded checks.
I think you countered your own argument when you suggested exceptions
can be implemented with longjump, which is closer than goto.

Closer to what?
plenty of techniques for writing exception safe code. I can't think of
any for writing goto safe code!

If exceptions simply did a longjump, people wouldn't use them.


Fair enough; longjmp cannot call destructors automatically like
C++/Java/etc exceptions can but it *can* unwind the stack.
The jmp_buf initialised with setjmp only gets invalidated when the
function scope where setjmp is called returns[1]. I'd assume that,
with language support for destructors (variables going out of
scope automatically get a certain function called), exceptions
can be implemented with longjmp.
True, but we were talking about goto.

Fair enough; I sorta dragged longjmp into this argument because
I consider it to be fairly close in function to a goto. We can
always drop the longjmp argument if you feel that it's a little
more structured.

Back to gotos: I still think that they have a use; sure they
can make code unstructured, but then again just about any
construct can be abused to make code unstructured including
while, for, do and if.

I've never actually *used* goto in any of my code (well, maybe
once :) but as a maintainer I see "goto error_exit" all the
time in code that is reasonably easy to read.

OTOH, I've had occasion to completely rewrite some "code"
(and I use that term loosely!) because it was so badly written
that it was faster to rewrite than try to figure out how it
worked and what all the status variables meant; yet the code
contained no gotos and only the usual suspects (while, for, etc).

Theres probably a lesson in there somewhere. Perhaps the
"goto error_exit" users are just more likely to have
structured their functions into "init, solve, exit" than
the coders who avoid goto at all costs.

goose,
 
I

Ian Collins

goose said:
Thats right; However I remain to be convinced that this is such
a bad thing. For a function of, say, ten lines (with the error-handling
code) I'd not hesitate to use a goto *when the error handler is
in the same scope*. If exceptions are available, I'd rather throw
an exception but if exceptions are *not* there for me to use
I'd chuck in a goto rather than have the logic code do
unneeded checks.
Well I certainly couldn't justify a goto in a ten line function, then
again I seldom write functions much longer than 10 lines :)
I think you countered your own argument when you suggested exceptions
can be implemented with longjump, which is closer than goto.


Closer to what?
Exceptions.
plenty of techniques for writing exception safe code. I can't think of
any for writing goto safe code!

If exceptions simply did a longjump, people wouldn't use them.


Fair enough; longjmp cannot call destructors automatically like
C++/Java/etc exceptions can but it *can* unwind the stack.
The jmp_buf initialised with setjmp only gets invalidated when the
function scope where setjmp is called returns[1]. I'd assume that,
with language support for destructors (variables going out of
scope automatically get a certain function called), exceptions
can be implemented with longjmp.

True, but we were talking about goto.
Back to gotos: I still think that they have a use; sure they
can make code unstructured, but then again just about any
construct can be abused to make code unstructured including
while, for, do and if.
It's all a matter of taste and style. As my teams and I use Test Driven
Development, code is refactored often, so any construct that causes
problems with this isn't appropriate.
I've never actually *used* goto in any of my code (well, maybe
once :) but as a maintainer I see "goto error_exit" all the
time in code that is reasonably easy to read.
Ah, the truth emerges!
 
D

Dave Vandervies

Ronald Bruck said:
I am reminded of an article I read at the height of the "GOTO"
controversy, way back in the 1970's: the author proposed
(tongue-in-cheek) a "COMEFROM" instruction. And he gave an example to
compute the factorial of 3.

The problem with the "COMEFROM" obstruction, of course, is that you had
^^^^^^^^^^^
Was that supposed to be "abstraction", or was it intentional?
(Either way, I like it.)
to scan THE ENTIRE REST OF THE CODEBASE to see if you were supposed to
divert to that instruction. His sample program to compute 3! was the
worst spaghetti I ever saw.

I've actually used setjmp/longjmp to implement a COME FROM in production
code. We had a program that knew how to shut down on a request from
another program, and we wanted to run it without having to start it from
a particular program (so that we could put it on another system) but
still start/stop the processing on request from the main control program.

There was a single function that did cleanup and exited, and it turned out
to be easiest to make sure everything that could end up invoking that was
longjmp-clean and just replace the cleanup-and-terminate with making sure
the internal state was in a known-sane condition and longjmping to just
after the one-time initialization and before the wait for a wake-up call.

(I've had at least two people try to tell me they had a better way.
I'm sure there is one, but neither of them had come up with it.
The original exit code could be called, through about three layers of
support functions, from anywhere the program spent significant amounts
of wall-clock time, so there wasn't any clean way to just fall out of
the main loop when it was time to go back to the beginning, and the
absence of a working time machine made it impossible take this design
change into consideration when the program was designed that way.)


Of course, using setjmp/longjmp is Rather Easier to implement and to
work with than a fully unconstrained COME FROM. Perhaps instead of
describing longjmp as a less-restricted goto, we should be describing
setjmp as a more-restricted COME FROM.


dave
 
C

Chris Torek

(On using longjmp in place of some form of built-in exception
handling...)

Fair enough; longjmp cannot call destructors automatically like
C++/Java/etc exceptions can but it *can* unwind the stack.

Well, maybe. "Can" is sometimes architecture-dependent, and
"does" is not actually true on many real implementations.
The jmp_buf initialised with setjmp only gets invalidated when the
function scope where setjmp is called returns[1]. I'd assume that,
with language support for destructors (variables going out of
scope automatically get a certain function called), exceptions
can be implemented with longjmp.

It is more complicated than that: in languages like Lisp (with
unwind-protect) and C++ (with destructors), you *must* "unwind
the stack", and transfer control (or act "as if" you had) to
those points at which runtime action is to be taken.

There are a lot of ways to implement this, but they all have some
costs, either in runtime for all functions, or in executable and/or
runtime-image space, or both. Since C lacks the requirement for
"one frame at a time unwinding", C implementations can save the
space and/or time -- and many do.
How is this different from exceptions? I might be way off here, but I
always assumed that exceptions could only unwind the stack, not magically
unlock or call free (or delete).

In a language that supports exceptions and has destructors (like
C++), the "unwind sequence" in longjmp() looks something like
this:

void longjmp(jmp_buf buf, int value) {
struct frame *fp = some_sort_of_magic();
struct frame *target_frame = more_magic(buf);

if (value == 0)
value = 1;
while (fp != target_frame) {
if (fp == NULL)
panic("bad call to longjmp");
if (fp->flags & UNWIND_ACTION)
(*fp->unwind)(fp);
fp = fp->prev_frame;
}
yet_more_magic(buf, value); /* resume at setjmp, returning "value" */
}

In C, it often looks instead like this:

void longjmp(jmp_buf buf, int value) {
if (value == 0)
value = 1;
magic(buf, value); /* resume at setjmp, returning "value" */
}

Note that the C++ "longjmp" is much heavier-weight, and requires
every function-call to set up fp->flags and (if the flags include
"take action on unwind") fp->unwind.

In both cases, the final magic sets the frame and/or stack pointers.

On some architectures, there is no separate "frame pointer", and
each subroutine can make up its own "virtual frame". If this
is carried through to the C++-style longjmp, the longjmp() code
becomes substantially more convoluted, as the last line of the
loop becomes:

while (fp != target_frame) {
...
fp = reconstruct_previous_frame(fp);
}

where "reconstruct previous frame" is arbitrarily complicated (it
may have to look in a table to correlate a "program counter" value
with a "function address range", then use that to find a "virtual
frame pointer offset" given a saved-stack-pointer; this is how it
works on MIPS processors for instance).

The shorter, C-specific "longjmp" remains unchanged, even on the
MIPS. (The jmp_buf can simply record the stack pointer and PC.
Going to the target PC automatically restores the virtual frame
pointer. Or, usage of setjmp() may force an actual frame pointer,
just like using a C99 VLA. In this case the "now-real" fp register
can be stored in the jmp_buf, along with the always-real sp. Note
that in either case, longjmp()ing wipes out VLAs allocated after
the call to setjmp().)
 
N

Nick Keighley

Richard said:
On the whole, C is still written by hackers, and it shows.

you say that like it's a bad thing...

you'll love this code I encountered earlier:-

void f ()
{
/* i is static and initialised elsewhere */
while (i < max)
{
process_item (i++);
break;
}
}
 
R

Richard Heathfield

Nick Keighley said:
you say that like it's a bad thing...

Um. Yes, no, maybe. Neither, really. Just an observation.

I suppose the thing is this - that C /can/ be written "properly", i.e. how I
would write it (modulo bugs), but to do so takes time and effort. Having
said that, it can also be written "improperly" - hack it, get it working,
move on to the next brilliant idea. C lets you do that. It doesn't get in
the way, doesn't make value judgements, doesn't berate you for hacking out
something that does the job. With my comp.lang.c hat on, or when writing
production code for other people (or indeed for myself), I write "proper"
C. But when nobody's looking (particularly me) and it's 3am and I'm on a
roll...
you'll love this code I encountered earlier:-

void f ()
{
/* i is static and initialised elsewhere */
while (i < max)
{
process_item (i++);
break;
}

Er... :)
 
B

Bob Martin

in 688813 20060728 131039 Richard Heathfield said:
I suppose the thing is this - that C /can/ be written "properly", i.e. how I
would write it (modulo bugs), but to do so takes time and effort. Having
said that, it can also be written "improperly" - hack it, get it working,
move on to the next brilliant idea. C lets you do that. It doesn't get in
the way, doesn't make value judgements, doesn't berate you for hacking out
something that does the job. With my comp.lang.c hat on, or when writing
production code for other people (or indeed for myself), I write "proper"
C. But when nobody's looking (particularly me) and it's 3am and I'm on a
roll...

All true, of course, but I don't see how this makes C different from any other language.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,065
Latest member
OrderGreenAcreCBD

Latest Threads

Top