Actually on that page, break, continue, and goto are listed under a section called "jump statements." The reason why I called it not a control structure is that goto is always 100% unconditional while the other ones choose which statement is the next statement based on a condition are are the only statements which allow the program to "branch" to multiple execution paths. You can have a program with 50 gotos but there will still only be one and only one possible path of execution.
But simple almost-arbitrary semantics is not the topic here. I agree that goto should be avoided. But once I saw a scheme for goto in C that I really liked -- and that was for error handling. If you want to do true error-handling in C you need to check for the return of every single function you call, and if you have done any resource allocation, if you want a truly correct program you have to clean up. For example
int myFunction() {
char* myMemory = malloc( 300 );
Resource otherResource = NULL;
FILE* f = fopen( "myfile.txt", "r" );
int errCode;
errCode = performFunc1( myMemory, f );
if ( errCode != SUCCESS_CODE ) {
free( myMemory );
fclose( f );
logError( "Error happened: %s", getErrorString( errCode ) );
return ERROR_CODE;
}
errCode = performFunc2( myMemory, f );
if ( errCode != SUCCESS_CODE ) {
free( myMemory );
fclose( f );
logError( "Error happened: %s", getErrorString( errCode ) );
return ERROR_CODE;
}
otherResource = makeResource();
errCode = performFunc3( otherResource, myMemory, f );
if ( errCode != SUCCESS_CODE ) {
free( myMemory );
fclose( f );
destroyResource( otherResource );
logError( "Error happened: %s", getErrorString( errCode ) );
return ERROR_CODE;
}
errCode = performFunc4( otherResource, myMemory, f );
if ( errCode != SUCCESS_CODE ) {
free( myMemory );
fclose( f );
destroyResource( otherResource );
logError( "Error happened: %s", getErrorString( errCode ) );
return ERROR_CODE;
}
return SUCCESS_CODE;
}
int myFunctionGoto() {
char* myMemory = malloc( 300 );
Resource otherResource = NULL;
FILE* f = fopen( "myfile.txt", "r" );
int errCode;
retCode = performFunc1( myMemory, f );
if ( errCode != SUCCESS_CODE ) goto cleanUp;
errCode = performFunc2( myMemory, f );
if ( errCode != SUCCESS_CODE ) goto cleanUp;
otherResource = makeResource();
errCode = performFunc3( otherResource, myMemory, f );
if ( errCode != SUCCESS_CODE ) goto cleanUp;
errCode = performFunc4( otherResource, myMemory, f );
if ( errCode != SUCCESS_CODE ) goto cleanUp;
cleanUp:
free( myMemory );
fclose( f );
destroyResource( otherResource );
if ( errCode != SUCCESS_CODE ) {
logError( "Error happened: %s", getErrorString( errCode ) );
return ERROR_CODE;
} else {
return SUCCESS_CODE;
}
}
In this second case, I believe that the goto greatly increases the readability code quality by not duplicating the cleanup code. Resource deallocation and error handling are in one spot and is not duplicated. If I add a new resource I only have to put the cleanup in one place. Since free and destroyResource can handle NULL, I don't have to check for this.
Now, in C++ I would definitely use RAII for this and never goto. RAII is not only shorter, easier to program, easier to read, and cleaner than the goto solution but it is also premature-return and exception-safe automatically with no additional work.