In Defence of GOTO
Back when I was studying for my Computing (Computer Science) A-Level in the noughties I remember how animated the teacher was when lecturing against the use of the goto
operator. They decried its usage as bad but I don’t recall them ever actually explaining why. Many years later working in industry I encountered similar hostility from a developer when I’d used it in some code I was hoping to merge. The goto
operator’s status as a programming pariah seems to originate from an essay by programming icon Dijkstra titled Go To Statement Considered Harmful1,2. The essay is worth a read and is essentially saying that proliferated use of goto
can result in a program that becomes extremely difficult to follow when parsing the code. Dijkstra explains that the contents of a variable can only be understood respective to the current position (“Progress”1) through the program, in which operations are computed sequentially. He argues that using goto
throws all of this out the window and makes the “Progress” throughout the program almost impossible to follow1. I do generally agree with the point he is making but I speculate that over the years the programming canon has established an absolutist THOU SHALL NOT USE GOTO3 mantra without actually understanding why and without consideration that there may be very specific cases where it can be used. Indeed Dijkstra states that goto
“should be abolished from all higher level programming languages”.1
In my experience sometimes it is OK to use goto
. I remember searching online for best practices for breaking out of nested loops in C and came across this4 answer on Stack Overflow which suggested the use of goto
. In my opinion this is a much cleaner and straight forward solution rather than maintaining various flags and if-statements to break out of the loops. Similarly it is also useful for escaping nested code where the current operation relies on successful operation of the previous. The MISRA C 2012 specification (which outlines various guidelines for writing C on safety critical systems) interestingly suggests the use of goto
for such scenarios to make the control flow more transparent (Rules 15.1, 15.2, 15.3 and 15.4)5,6.
Example
The code snippet below shows some code in C for connecting to a TCP socket on a POSIX system7. Following through the numbered steps you can see that each operation of the TCP_Connect
function relies on the previous to succeed. This results on many nested if-statements that can become unwieldy as more steps are added.
bool TCP_Connect(int * sock, char * ip, char * port)
{
bool ret = false;
int status;
struct addrinfo hints;
struct addrinfo *servinfo;
memset( &hints, 0U, sizeof( hints ) );
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
/* 1. Resolve ip + port info */
status = getaddrinfo( ip, port, &hints, &servinfo );
if( status != 0U )
{
fprintf( stderr, "TCP Error: %s\n", gai_strerror( status ) );
ret = false;
}
else
{
/* 2. Create Socket */
sock = socket( servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol);
if( sock < 0 )
{
printf("Error creating socket\n");
perror("Error:");
ret = false;
}
else
{
/* 3. Attempt Connection */
int c = connect( sock, servinfo->ai_addr, servinfo->ai_addrlen);
if( c < 0 )
{
printf("\tConnection Failed\n");
perror("Error:");
ret = false;
}
else
{
/* 4. We have liftoff */
ret = true;
}
}
}
return ret;
}
My preferred approach (which looks cleaner in my opinion) is to instead utilise the goto
operator and to jump to the end of the function should one of the necessary operations fails. The following function rewrites the TCP_Connect
function to utilise goto
instead of the nested if-statements. Should one of the operations fail the goto cleanup
statement allows the program to essentially leap-frog over the remaining operations and return from the function.
bool TCP_Connect(int * sock, char * ip, char * port)
{
bool ret = false;
int status;
struct addrinfo hints;
struct addrinfo *servinfo;
memset( &hints, 0U, sizeof( hints ) );
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
/* 1. Resolve ip + port info */
status = getaddrinfo( ip, port, &hints, &servinfo );
if( status != 0U )
{
fprintf( stderr, "Comms Error: %s\n", gai_strerror( status ) );
goto cleanup;
}
/* 2. Create Socket */
sock = socket( servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol);
if( sock < 0 )
{
printf("Error creating socket\n");
perror("Error:");
goto cleanup;
}
/* 3. Attempt Connection */
int c = connect( sock, servinfo->ai_addr, servinfo->ai_addrlen);
if( c < 0 )
{
printf("\tConnection Failed\n");
perror("Error:");
goto cleanup;
}
/* 4. We have liftoff */
ret = true;
cleanup:
return ret;
}
You’re probably wondering why the function doesn’t just return
instead of using the goto
operator to jump to the end of the function. The choice to use goto
is to ensure that the function only has a single point of exit, which is one of MISRA’s advisory rules for control flow (15.6 to be specific in the 2012 version)5. I’m personally a fan of this rule as it makes functions a lot easier to read and follow.
Conclusion
Sometimes using goto
is OK. :)
-
Edgar Dijkstra - Go To Statement Considered Harmful link ↩ ↩2 ↩3 ↩4
-
The secret 11th commandment ↩
-
MISRA C:2012 3rd edition 1st Revision - 8.15 Control flow . February 2019. ↩ ↩2
-
I’m intentionally being opaque about the specific wording of these directives as MISRA is a proprietary specification, though it can be acquired via other means. ↩
-
Developed with help from “Beej’s Guide to Network Programming” link ↩