Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid use of goto and break nested loops efficiently

Tags:

c++

c

c++11

goto

I'd say that it's a fact that using goto is considered a bad practice when it comes to programming in C/C++.

However, given the following code

for (i = 0; i < N; ++i) 
{
    for (j = 0; j < N; j++) 
    {
        for (k = 0; k < N; ++k) 
        {
            ...
            if (condition)
                goto out;
            ...
        }
    }
}
out:
    ...

I wonder how to achieve the same behavior efficiently not using goto. What i mean is that we could do something like checking condition at the end of every loop, for example, but AFAIK goto will generate just one assembly instruction which will be a jmp. So this is the most efficient way of doing this I can think of.

Is there any other that is considered a good practice? Am I wrong when I say it is considered a bad practice to use goto? If I am, would this be one of those cases where it's good to use it?

Thank you

like image 819
rual93 Avatar asked May 25 '18 13:05

rual93


People also ask

How do you avoid nested loops?

You can avoid nested loops with itertools. product() . You can use itertools. product() to get all combinations of multiple lists in one loop and get the same result as nested loops.

Why goto is not preferred?

NOTE − Use of goto statement is highly discouraged in any programming language because it makes difficult to trace the control flow of a program, making the program hard to understand and hard to modify. Any program that uses a goto can be rewritten to avoid them.

Is goto faster than loop?

Generally speaking, for and while loops get compiled to the same thing as goto , so it usually won't make a difference. If you have your doubts, you can feel free to try all three and see which takes longer. Odds are you'll be unable to measure a difference, even if you loop a billion times.

Does goto break loop?

@MatheusRocha Yes, it does. In fact, C++ has special rules about goto that prevent programmers from making improper jumps, e.g. jumping over initialization.


4 Answers

The (imo) best non-goto version would look something like this:

void calculateStuff()
{
    // Please use better names than this.
    doSomeStuff();
    doLoopyStuff();
    doMoreStuff();
}

void doLoopyStuff()
{
    for (i = 0; i < N; ++i) 
    {
        for (j = 0; j < N; j++) 
        {
            for (k = 0; k < N; ++k) 
            {
                /* do something */

                if (/*condition*/)
                    return; // Intuitive control flow without goto

                /* do something */
            }
        }
    }
}

Splitting this up is also probably a good idea because it helps you keep your functions short, your code readable (if you name the functions better than I did) and dependencies low.

like image 81
Max Langhof Avatar answered Oct 09 '22 23:10

Max Langhof


If you have deeply-nested loops like that and you must break out, I believe that goto is the best solution. Some languages (not C) have a break(N) statement that will break out of more than one loop. The reason C doesn't have it is that it's even worse than a goto: you have to count the nested loops to figure out what it does, and it's vulnerable to someone coming along later and adding or removing a level of nesting, without noticing that the break count needs to be adjusted.

Yes, gotos are generally frowned upon. Using a goto here is not a good solution; it's merely the least of several evils.

In most cases, the reason you have to break out of a deeply-nested loop is because you're searching for something, and you've found it. In that case (and as several other comments and answers have suggested), I prefer to move the nested loop to its own function. In that case, a return out of the inner loop accomplishes your task very cleanly.

(There are those who say that functions must always return at the end, not from the middle. Those people would say that the easy break-it-out-to-a-function solution is therefore invalid, and they'd force the use of the same awkward break-out-of-the-inner-loop technique(s) even when the search was split off to its own function. Personally, I believe those people are wrong, but your mileage may vary.)

If you insist on not using a goto, and if you insist on not using a separate function with an early return, then yes, you can do something like maintaining extra Boolean control variables and testing them redundantly in the control condition of each nested loop, but that's just a nuisance and a mess. (It's one of the greater evils that I was saying using a simple goto is lesser than.)

like image 33
Steve Summit Avatar answered Oct 10 '22 01:10

Steve Summit


In this case you don't wan't to avoid using goto.

In general the use of goto should be avoided, however there are exceptions to this rule, and your case is a good example of one of them.

Let's look at the alternatives:

for (i = 0; i < N; ++i) {
    for (j = 0; j < N; j++) {
        for (k = 0; k < N; ++k) {
            ...
            if (condition)
                break;
            ...
        }
        if (condition)
            break;
    }
    if (condition)
        break;
}

Or:

int flag = 0
for (i = 0; (i < N) && !flag; ++i) {
    for (j = 0; (j < N) && !flag; j++) {
        for (k = 0; (k < N) && !flag; ++k) {
            ...
            if (condition) {
                flag = 1
                break;
            ...
        }
    }
}

Neither of these is as concise or as readable as the goto version.

Using a goto is considered acceptable in cases where you're only jumping ahead (not backward) and doing so makes your code more readable and understandable.

If on the other hand you use goto to jump in both directions, or to jump into a scope which could potentially bypass variable initialization, that would be bad.

Here's a bad example of goto:

int x;
scanf("%d", &x);
if (x==4) goto bad_jump;

{
    int y=9;

// jumping here skips the initialization of y
bad_jump:

    printf("y=%d\n", y);
}

A C++ compiler will throw an error here because the goto jumps over the initialization of y. C compilers however will compile this, and the above code will invoke undefined behavior when attempting to print y which will be uninitialized if the goto occurs.

Another example of proper use of goto is in error handling:

void f()
{
    char *p1 = malloc(10);
    if (!p1) {
        goto end1;
    }
    char *p2 = malloc(10);
    if (!p2) {
        goto end2;
    }
    char *p3 = malloc(10);
    if (!p3) {
        goto end3;
    }
    // do something with p1, p2, and p3

end3:
    free(p3);
end2:
    free(p2);
end1:
    free(p1);
}

This performs all of the cleanup at the end of the function. Compare this to the alternative:

void f()
{
    char *p1 = malloc(10);
    if (!p1) {
        return;
    }
    char *p2 = malloc(10);
    if (!p2) {
        free(p1);
        return;
    }
    char *p3 = malloc(10);
    if (!p3) {
        free(p2);
        free(p1);
        return;
    }
    // do something with p1, p2, and p3

    free(p3);
    free(p2);
    free(p1);
}

Where the cleanup is done in multiple places. If you later add more resources that need to be cleaned up, you have to remember to add the cleanup in all of these places plus the cleanup of any resources that were obtained earlier.

The above example is more relevant to C than C++ since in the latter case you can use classes with proper destructors and smart pointers to avoid manual cleanup.

like image 12
dbush Avatar answered Oct 10 '22 01:10

dbush


I think that goto is a perfectely sane thing to do here, and is one of it's exceptional use cases per the C++ Core Guidelines.

However, perhaps another solution to be considered is an IIFE lambda. In my opinion this is slightly more elegant than declaring a separate function!

[&] {
    for (int i = 0; i < N; ++i)
        for (int j = 0; j < N; j++)
            for (int k = 0; k < N; ++k)
                if (condition)
                    return;
}();

Thanks to JohnMcPineapple on reddit for this suggestion!

like image 17
ricco19 Avatar answered Oct 09 '22 23:10

ricco19