breaking out of multiple nested loops

W

William Hughes

C has no multi-level break statement.  Use a goto statement.

No, really.

It can also be argued (and undoubtedly will be) that if you find
yourself needing a multi-level break, your code is too complicated,
and you should restructure the code so it no longer needs it.



Well, it may be possible to restructure and
simplify the code, but there
are times when this type of structure is needed, e.g.
searching for an element is a three dimensional matrix.
You have to do three loops and end processing when
an element is found. Disguising what you are doing
will not make things magically simpler.

My own preference is a labeled break.
C does not have this construct so use a goto.
There is nothing wrong with branching.
(Programming would be very difficult without
a branch. It is unrestricted branching that
leads to trouble.)

Rules for goto

1. Use very sparingly

2. Never jump backward or into a loop

3 Only jump to the end of a loop

4. If you are optimizing code
ignore the rules [Recall the rules
of optimziation, 1. Dont do it
(for experts) 2. Don't do it yet]

(Note that rules 2 and 3 can be added to
a compiler as warnings, so a language change
is not needed.)



- William Hughes
 
B

Ben Bacarisse

William Hughes said:
Well, it may be possible to restructure and
simplify the code, but there
are times when this type of structure is needed, e.g.
searching for an element is a three dimensional matrix.
You have to do three loops and end processing when
an element is found. Disguising what you are doing
will not make things magically simpler.

Disguising? Maybe. Just to see how it comes out, this is how I'd do
it so as not to have a multi-level break:

bool array_1d_find(int what, size_t n, int array[n], size_t *where)
{
for (size_t i = 0; i < n; i++)
if (array == what)
return *where = i, true;
return false;
}

bool array_2d_find(int what, size_t n, size_t m,
int array[n][m], size_t *where)
{
for (size_t i = 0; i < n; i++)
if (array_1d_find(what, m, array, where+1))
return *where = i, true;
return false;
}

bool array_3d_find(int what, size_t n, size_t m, size_t o,
int array[n][m][o], size_t *where)
{
for (size_t i = 0; i < n; i++)
if (array_2d_find(what, m, o, array, where+1))
return *where = i, true;
return false;
}

and a call looks like this:

size_t ind[3];
if (array_3d_find(99, 3, 4, 5, M, ind))
/* use M[ind[0]][ind[1]][ind[2]]
or set size_t i = idx[0], j = idx[1], k = idx[2]; if you
intend to use them a lot.
*/

I don't think this disguises anything very much. This nice thing is
that the auxiliary functions are independently useful.
My own preference is a labeled break.
C does not have this construct so use a goto.
There is nothing wrong with branching.
(Programming would be very difficult without
a branch. It is unrestricted branching that
leads to trouble.)

Rules for goto

1. Use very sparingly

2. Never jump backward or into a loop

3 Only jump to the end of a loop

I'd say "Only jump to the end or to just after a loop". To my mind,
jumping to the end is like continue, and jumping to just after a loop
is like break.
4. If you are optimizing code
ignore the rules [Recall the rules
of optimziation, 1. Dont do it
(for experts) 2. Don't do it yet]

(Note that rules 2 and 3 can be added to
a compiler as warnings, so a language change
is not needed.)
 
B

bartc

Ben Bacarisse said:
Well, it may be possible to restructure and
simplify the code, but there
are times when this type of structure is needed, e.g.
searching for an element is a three dimensional matrix.
You have to do three loops and end processing when
an element is found. Disguising what you are doing
will not make things magically simpler.

Disguising? Maybe. Just to see how it comes out, this is how I'd do
it so as not to have a multi-level break:

bool array_1d_find(int what, size_t n, int array[n], size_t *where)
{
for (size_t i = 0; i < n; i++)
if (array == what)
return *where = i, true;
return false;
}

bool array_2d_find(int what, size_t n, size_t m,
int array[n][m], size_t *where)
{
for (size_t i = 0; i < n; i++)
if (array_1d_find(what, m, array, where+1))
return *where = i, true;
return false;
}

bool array_3d_find(int what, size_t n, size_t m, size_t o,
int array[n][m][o], size_t *where)
{
for (size_t i = 0; i < n; i++)
if (array_2d_find(what, m, o, array, where+1))
return *where = i, true;
return false;
}

and a call looks like this:

size_t ind[3];
if (array_3d_find(99, 3, 4, 5, M, ind))
/* use M[ind[0]][ind[1]][ind[2]]
or set size_t i = idx[0], j = idx[1], k = idx[2]; if you
intend to use them a lot.
*/

I don't think this disguises anything very much. This nice thing is
that the auxiliary functions are independently useful.


You really think the above is simpler and clearer than this:

a=6;
found=0;

for (i=0; i<dx; ++i)
for (j=0; j<dy; ++j)
for (k=0; k<dz; ++k)
if (data[j][k]==a) {found=1; goto l;}
l:
if (found) printf("Found at [%d][%d][%d]\n",i,j,k);

?

Of course you might change 'goto l' to 'break 2' or 'break 3', or 'break
<label>'; since it's only only jumping to the very next line, trying to find
where it ends up can't be too taxing.
 
B

Ben Bacarisse

bartc said:
Ben Bacarisse said:
Well, it may be possible to restructure and
simplify the code, but there
are times when this type of structure is needed, e.g.
searching for an element is a three dimensional matrix.
You have to do three loops and end processing when
an element is found. Disguising what you are doing
will not make things magically simpler.

Disguising? Maybe. Just to see how it comes out, this is how I'd do
it so as not to have a multi-level break:

bool array_1d_find(int what, size_t n, int array[n], size_t *where)
{
for (size_t i = 0; i < n; i++)
if (array == what)
return *where = i, true;
return false;
}

bool array_2d_find(int what, size_t n, size_t m,
int array[n][m], size_t *where)
{
for (size_t i = 0; i < n; i++)
if (array_1d_find(what, m, array, where+1))
return *where = i, true;
return false;
}

bool array_3d_find(int what, size_t n, size_t m, size_t o,
int array[n][m][o], size_t *where)
{
for (size_t i = 0; i < n; i++)
if (array_2d_find(what, m, o, array, where+1))
return *where = i, true;
return false;
}

and a call looks like this:

size_t ind[3];
if (array_3d_find(99, 3, 4, 5, M, ind))
/* use M[ind[0]][ind[1]][ind[2]]
or set size_t i = idx[0], j = idx[1], k = idx[2]; if you
intend to use them a lot.
*/

I don't think this disguises anything very much. This nice thing is
that the auxiliary functions are independently useful.


You really think the above is simpler and clearer than this:

a=6;
found=0;

for (i=0; i<dx; ++i)
for (j=0; j<dy; ++j)
for (k=0; k<dz; ++k)
if (data[j][k]==a) {found=1; goto l;}
l:
if (found) printf("Found at [%d][%d][%d]\n",i,j,k);

?


No, and I did not say it was. I said that the above does not disguise
what is happening and even that had a "maybe" attached.

The advantage comes from the fact the components do a job of their
own. You may not get any benefit from that in the program you are
writing now, but I have rarely regretted writing a function instead of
a loop in the long run.

<snip>
 
B

bartc

Richard Heathfield said:
<smug>
Yes. This is one of the many pay-offs I get from my excessively
strict self-imposed structuring rules - I consider break to be usable
*only* within a switch.



And my approach requires neither a change to the structure of my
code nor any kind of fix whatsoever.
</smug>

At a cost of having to write convoluted code in the first place..

BTW suppose you have a switch-break statement inside an if-else chain inside
a switch, and you change that if-else to a nested switch; how do you now
break out of two levels of switch?
 
J

John Kelly

In my experience only real idiots objects to goto in all situations.

Maybe they're not idiots but just lack practical experience. People cut
off their hands with chainsaws, but that doesn't prove chainsaws should
never be used. You just have to be careful. And it helps to know what
you're doing.
 
J

John Kelly

No. Idiots. And I say idiots because they are unable to see past their
own self perceived perfection in structuring code.

I have observed religious fervor here. I'm more inclined to live and
let live. I didn't expect the maniacal reaction of a few to dh and its
code. From my casual reading of this ng, it seems Jacob and I are the
only ones who talk about their own project code. He was the only one to
welcome me when I first posted mine.

More project code and less bombast would make this a better ng.
 
K

Kenny McCormack

John Kelly said:
More project code and less bombast would make this a better ng.

(Semi-serious mode) "You would think..."

(Serious mode) "Yes, you are absolutely right that no one (other than
newbies) ever posts actual code here. It's just asking to be
clobbered. In fact, if you didn't get clobbered for it, it would be
proof positive that the regs weren't doing their jobs."

(Satirical, but serious mode) "Anything at all tangible is OT. There is
no mention of dh or lcc, or anything else that sensible people might
actually want to discuss, in the C standards documents."
 
J

John Kelly

(Serious mode) "Yes, you are absolutely right that no one (other than
newbies) ever posts actual code here. It's just asking to be
clobbered. In fact, if you didn't get clobbered for it, it would be
proof positive that the regs weren't doing their jobs."

I can tolerate the clobbering. Though little, some good came from it.
But once they take their position and I take mine, there's no point in a
forever loop. That's where I break out. Or goto have fun. ;-)
 
N

Nick

Eric Sosman said:
This is how Java does it, and I for one find it repugnant.
Although `continue foo;' reads reasonably well, `break foo;' is
just plain awful: It says "transfer control to somewhere *not*
close to the label foo." To discover where the destination is,
you've got to find the label and then search for the other end
of its block -- and we must assume that the block is non-trivial,
or a multi-level break wouldn't have been needed in the first
place ... (On a labelled do-while, `continue foo;' shares the
same problem of misdirected attention.)

But since there's precedent, somebody will probably follow
it, alas. Java adopted some of C's infelicities; why shouldn't
C incorporate the unpleasant features of Java?

It certainly predates Java. I first came across it in "Superbasic" on
the Sinclair QL:

repeat foo
if a=y then exit foo
end repeat

OK, you could put the "foo" on the end of the repeat, but it wasn't
needed. I'm not even sure it was checked (ie, you might have been able
to get away with "end repeat bar" there).
 
R

Richard Tobin

Richard said:
"break" is not more than a glorified goto.

No, it's a *restricted* goto. One of the objections to goto is that
it can result in "spaghetti code". Of course, not all uses of goto
have that result. A goto used as a break doesn't, but break has
the advantage of making clear locally that it doesn't.

-- Richard
 
P

Phil Carmody

Keith Thompson said:
But since neither "break <number>" nor "break <LABEL>" would, ahem,
break any existing code, I really can't think of any sane reason to
*prefer* the "break <number>" to "break <LABEL>".

Can you?

Yes. Simpler and clearer.

What if you try to break to a label which doesn't correspond to
a loop?

Phil
 
I

Ike Naar

Yes. Simpler and clearer.

What if you try to break to a label which doesn't correspond to
a loop?

What if you try to "break <number>" when the number of loops you're in
is less than "number"?
 
K

Keith Thompson

Phil Carmody said:
Yes. Simpler and clearer.

Really? I disagree completely on both counts.

Consider a loop within a switch statement within a loop. Suppose I
want to break out of the outer loop from within the inner loop;
do I use "break 2;" or "break 3;"? What if I just want to finish
the current iteration of the outer loop; is that "continue 2;"
or "continue 3;"? Sure, it might be poor style, but the language
would have to define it.

But even in ordinary usage, I find names clearer than numbers.

Perl has this feature, and I often use it even for non-nested loops,
just because it makes the code clearer. Perl's equivalent of
"break" is "last". It's not uncommon to have an outer loop that
iterates over files and an inner loop that iterates over lines.
I can write "last LINE;" or "last FILE;" to terminate either the
inner loop or the outer loop; I find that *much* clearer than
"last 1;" or "last 2;".

At least one other person has complained that "break LABEL;"
is unclear because it jumps, not to LABEL, but to some point
after LABEL. The trick is that, when it's the used with a break or
continue statement, a label doesn't name a single point in the code;
it's the name of the loop as a whole. Assigning a name to a loop,
to be used when you want to do something related to that loop, makes
just as much sense to me as assigning a name to a function or object.
What if you try to break to a label which doesn't correspond to
a loop?

It would be a constraint violation.
 
P

Phil Carmody

What if you try to "break <number>" when the number of loops you're in
is less than "number"?

What if you try to "goto <label>" when the function you're in does not
contain a label <label>?

Fortunately compilers are as good at counting as they are at doing
string comparison, so I think most should cope with all three equally.

Phil
 
K

Keith Thompson

Phil Carmody said:
[SNIP - deep loop escapes]
But since neither "break <number>" nor "break <LABEL>" would, ahem,
break any existing code, I really can't think of any sane reason to
*prefer* the "break <number>" to "break <LABEL>".

Can you?

Yes. Simpler and clearer.

What if you try to break to a label which doesn't correspond to
a loop?

What if you try to "break <number>" when the number of loops you're in
is less than "number"?

What if you try to "goto <label>" when the function you're in does not
contain a label <label>?

Fortunately compilers are as good at counting as they are at doing
string comparison, so I think most should cope with all three equally.

Certainly. Nobody (I think) is suggesting that the difficulty
of the compiler getting this right is an issue. However it's
defined, as long as it's defined rigorously (including the
loop-with-switch-within-loop break vs. continue case), implementing
it won't be a problem. There's ample precedent in other languages.

My point is that, as a programmer, I find "break LOOP_NAME;" to be
much clearer and easier to use and maintain than "break COUNT;".

Would you want a function call construct that makes you refer to the
Nth function in the current translation unit? Or a struct member
syntax that makes you refer to the Nth member?

Computers refer to things by number. Programmers use symbolic names.
It's the compiler's job to translate one to the other.
 
P

Phil Carmody

Keith Thompson said:
Phil Carmody said:
[SNIP - deep loop escapes]
But since neither "break <number>" nor "break <LABEL>" would, ahem,
break any existing code, I really can't think of any sane reason to
*prefer* the "break <number>" to "break <LABEL>".

Can you?

Yes. Simpler and clearer.

What if you try to break to a label which doesn't correspond to
a loop?

What if you try to "break <number>" when the number of loops you're in
is less than "number"?

What if you try to "goto <label>" when the function you're in does not
contain a label <label>?

Fortunately compilers are as good at counting as they are at doing
string comparison, so I think most should cope with all three equally.

Certainly. Nobody (I think) is suggesting that the difficulty
of the compiler getting this right is an issue. However it's
defined, as long as it's defined rigorously (including the
loop-with-switch-within-loop break vs. continue case), implementing
it won't be a problem. There's ample precedent in other languages.

My point is that, as a programmer, I find "break LOOP_NAME;" to be
much clearer and easier to use and maintain than "break COUNT;".

Would you want a function call construct that makes you refer to the
Nth function in the current translation unit? Or a struct member
syntax that makes you refer to the Nth member?

Computers refer to things by number. Programmers use symbolic names.
It's the compiler's job to translate one to the other.

I've used label-less languages with counted breaks, and it all seemed
perfectly natural, so perhaps my point of view is tainted with that
experience.

Phil
 
R

Richard Bos

bartc said:
I have preference for break <no>, even though it looks crude, as break
<label> is not actually much different from goto <label>, and it means
thinking up a label in the first place and then remembering to add it at one
end of the loop or the other.

Yes, that's exactly why break <label> would be _better_. It forces you
to be explicit about what you want.

Richard
 
B

bartc

Richard Bos said:
Yes, that's exactly why break <label> would be _better_. It forces you
to be explicit about what you want.

I remember driving in Germany where the Autobahn exits are given names
related to the locality instead of being numbered sequentially.

Which means that you have no idea when approaching an exit whether you've
missed your exit, or there's still some way to go.

In the same way, the number in break <number> gives you information that a
labeled break doesn't: whether you are breaking out of an inner, outer or
middle loop (as indicated by the current indentation).
 

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

Similar Threads


Members online

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,074
Latest member
StanleyFra

Latest Threads

Top