If Goto is "considered harmful," then so can...

Pages: 123
@Disch,
I wasn't using goto for that in C++, only in C. In C++ I would use exceptions and let the destructors do the cleaning up for me.
A problem with control structures is the side-effect of their imperative nature. Control structures mostly work quite well given a very isolated (closed system) state, just as C initially permitted functions to be (merely with a few input parameters, and one output). I think problems arise when you don't have the simplicity of maintaining such a low-dimensional program state, or your design goal requires a highly dynamic control flow. This may severely complicate the programmer's task of determining such structures. Also, I'm definitely not convinced that control structures are the best solution to goto's problem.
Last edited on
This is the (stupid) pure control-structure alternative:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
bool BananaDance()
{
	bool success = true;
	
	// ...
	if (error)
	{
		success = false;
	}
	else
	{
		// ...
		if (error)
		{
			success = false;
		}
		else
		{
			// ...
			if (error)
			{
				success = false;
			}
		}
	}
	
	// free resources

	return success;
}
Another stupid way.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
bool BananDance() {

    int RetV = 1;
   
    while (RetV) {
        //...
        if (error1) {
            break;                
        }
        //... 
        if (error2) {
            break;
        }
        //...
        if (error3) {
            break;
        }
        //...
        RetV = 0;
    }  
    //free resources
    return RetV;
}


@webJose


The use of goto is related to spaghetti code. See http://en.wikipedia.org/wiki/Spaghetti_code .



For a person who doesn't know English, the page seems to be about Italian Gourmet. You've just made me hungry!


EDIT: I scrolled a bit down --> lol
Last edited on
@iseeplusplus

Invalid. You didn't spell "BananaDance" correctly.
There are certain cases when goto is O.K. to use. But using it as some kind of glorified function call is definitely a no-no with gotos.

Here's an example when goto is OK. And even this can be tweaked so the goto isn't necessary.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
while( !has_error())
{
    StartNextSegment();
    while(!has_error())
    {
         if( Segments_AllFinished() )
                  goto LOOP_OUT;
         segment_dopart();
         if( segmentIsDone() )
             break; //outer while()
    }
}

LOOP_OUT:; // 
Last edited on
Using goto is always very short sighted. Taking Nexius example.

When placing a line after the while loop, you have to decide whether it has to be before or after the label. This is hard enough because it depends on exact reason why and when that goto is called. If I am the one to modify the code that alone would give me the creeps. But what if someone has to modify the loop itself? ...god awful...
Good use of goto is seldom demonstrated with a simple example, as someone can always say "you can do it like this...". Simple examples have simple alternatives.

The short sightedness is threads like this is the knee jerk reaction, "never use X".

As for the whole, "I've had an error throw an exception", that is just as bad. Talk about using goto causes spaghetti code; at least you can see the label that to goto goes to, throw an exception and who knows where it will end up. Plus there may be times when you are not allowed to use exceptions, so knowing alternatives is always good.

Here's an example when goto is OK. And even this can be tweaked so the goto isn't necessary.


This is only because C++ lacks labeled break / continue. Anyway, I'm sceptic about using break and continue at all. You should try to write your loops in such a way that there is only one exit point - the loop condition. A long loop littered with breaks / continues is really not any more clear than a bunch of gotos.
Last edited on
The few times I've been tempted to use a goto, I ended up scratching the entire context and reworking it. I'm sure this isn't always the case, but in my work, if the flow isn't natural than it isn't right. So far, I've always ended up with better code after avoiding the goto-trap.
chrisname wrote:
@Disch,
I wasn't using goto for that in C++, only in C. In C++ I would use exceptions and let the destructors do the cleaning up for me.


I think exceptions are pretty controversial too.

I'm also sure throwing an exception is a hell of a lot slower than using goto or setting up an appropriate control structure.
The one case (snicker) where I find "goto" to be extremely useful is in handling switch statements where multiple cases share the a majority of code and differ only in the initial setup:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
switch ( inst_operation )
{
    case I_ADD_I8:  result = REG(dreg) + IMM8(iword);   goto alu_inst;
    case I_ADD_I16: result = REG(dreg) + IMM16(iword);  goto alu_inst;
    case I_ADD:     result = REG(dreg) + REG(sreg);     goto alu_inst;
    /* And many more ... */
    case I_SUB_I8:  result = REG(dreg) - IMM8(iword);   goto alu_inst;
    case I_SUB_I16: result = REG(dreg) - IMM16(iword);  goto alu_inst;
    case I_SUB:     result = REG(dreg) - REG(sreg);     goto alu_inst;
    case I_SUBR:    result = REG(sreg) - REG(dreg);     goto alu_inst;
      alu_inst:
        cc.zero = (result == 0);
        cc.neg  = (result & NEG32) ? TRUE : FALSE;
        /* save result ... */
        /* update pipeline for a simple ALU instruction ... */
        break;
    
    case I_BRA:                     iaddr = iaddr + SEXT12(iword << 1);  goto branch_inst;
    case I_BSR:  SetReg(A6,iaddr);  iaddr = iaddr + SEXT12(iword << 1);  goto branch_inst;
    case I_JMP:                     iaddr = REG(dreg) & INST_MASK;       goto branch_inst;
    case I_JSR:  SetReg(A6,iaddr);  iaddr = REG(dreg) & INST_MASK;       goto branch_inst;
      branch_inst:
        /* invalidate the instruction pipeline ... */
        /* start fetching the new instruction ... */
        break;
        
    /* Many more instruction operations */
}


I have seen code which avoids "goto" by nesting multiple switch statements, but I have also seen code where one of the cases is missing and such an error can only be caught at run-time:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
switch ( inst_operation )
{
    case I_ADD_I8:
    case I_ADD_I16:
    case I_ADD:
    case I_SUB_I8:
    case I_SUB_I16:
    case I_SUB:
    case I_SUBR:
        switch ( inst_operation )
        {
            case I_ADD_I8:  result = REG(dreg) + IMM8(iword);   break;
            case I_ADD_I16: result = REG(dreg) + IMM16(iword);  break;
            case I_ADD:     result = REG(dreg) + REG(sreg);     break;
            case I_SUB_I8:  result = REG(dreg) - IMM8(iword);   break;
            case I_SUB_I16: result = REG(dreg) - IMM16(iword);  break;
            case I_SUBR:    result = REG(sreg) - REG(dreg);     break;
        }
        cc.zero = (result == 0);
        cc.neg  = (result & NEG32) ? TRUE : FALSE;
        /* save result ... */
        /* update pipeline for a simple ALU instruction ... */
        break;
        
    /* Many more instruction operations */
}

Other solutions which I have seen involve functions for the shared code or subclasses for the case-specific code.
Veltas wrote:
I think exceptions are pretty controversial too.


They only seem to be controversial in C++. In pretty much every other language I've used that supports them, they are the norm.

I'm also sure throwing an exception is a hell of a lot slower than using goto or setting up an appropriate control structure.


That's what everyone says. But I have yet to experience any significant performance issues in any of my personal programs that I was able to attribute to exceptions.
mpauna wrote:
The one case (snicker) where I find "goto" to be extremely useful is in handling switch statements where multiple cases share the a majority of code and differ only in the initial setup:

I disagree: You can as easily just create a function with the common code. This is to me a classic example of misuse because a function call will look almost identical. I see no gains in using goto here. You can even inline the function if you must.

I think I'll even take the embedded switch approach here.
Last edited on
Switch statements are often just gotos. Something like
1
2
3
switch (variable) {
        // Some cases (at least 5)
}

will usually get compiled into something like this:
1
2
3
4
mov eax, variable // eax = result of switch expression
mov ebx, jump_table // Basically an array of addresses, each address being a different case
add ebx, eax
jmp [ebx] // Jump (goto) to the correct case 

which is why switches are often faster than if/else trees.
Last edited on
chrisname wrote:
Switch statements are often just gotos.


All control structures are gotos...

But switch statements are not only cleaner than using gotos but have more chance of being faster; switch statements can be heavily optimised.
Disch wrote:
They only seem to be controversial in C++. In pretty much every other language I've used that supports them, they are the norm.


But it was C++ we were talking about, right?

Also, exceptions are rather unique in their flexibility in C++, and they have a bigger adverse effect on performance in C++ than most other languages too, so that would be why.
webJose wrote:
I disagree: You can as easily just create a function with the common code. This is to me a classic example of misuse because a function call will look almost identical. I see no gains in using goto here. You can even inline the function if you must.

Context makes the difference, and that is where programming becomes an "art". What are the design goals? How many arguments are required for the function? Is it appropriate to seperate the functional blocks into seperate functions or are they more easily understood when grouped together? Is a list or a grid more appropriate for grouping the "data" embedded within the code. Is the code in the critical path for optimization?

The above code is (as everyone can probably guess) a pseudo-fragment of a CPU simulator. In this case, the ALU and branch instructions were on the critical optimization path and function calls would actually introduce a substantial performance penalty (the critical path was about 80 SPARC instructions and about a 100 IA32 instructions executed). In this case, inlining would be nice if the common code permitted inlining and if the optimizer was smart enough to tail-merge the case statements, neither of which was the case.

"goto" was a valuable tool when used in what we considered to be the right situtation. All of the cases were "table-ized" (with at most 3 or 4 variants per row) and the goto label immediately followed the cases.

Anyways, keep "goto" in the toolbox. In almost every case it may be the wrong tool but there are times when it is useful and appropriate.

webJose wrote:
I think I'll even take the embedded switch approach here.

Did you notice that the embedded switch was missing the I_SUB case? That was a simple example as I have seen both CPU simulation and message handling code where there are frequently over a dozen nested cases. I can't even count the number of times I have seen errors late in a project where code gets reorganized or new messages are added and a corresponding case statement is missed in one or the other switch statement. All of these cases have gotten past the developer's checkin and code reviews. Most are fortunately caught by testing, but even there they can be missed (especially if there is no default case to issue an error or fatal error, or if the error logs aren't analyzed for failures). Repetition of code is usually evil, but so it the repetition of values.
Veltas wrote:
All control structures are gotos...

I know, I said that in an earlier post. I said switch statements specifically because that's what was being talked about.

I don't really see how a goto can be optimised since non-conditional jumps are about as quick as they can get.
Pages: 123