3.12. Supplemental: Legitimate Uses For goto

Programmers rarely use the goto operator in modern programming, so you may skip this section if your instructor didn't explicitly assign it. Previously, the unrestrained use of goto statements caused many incomprehensible and unmaintainable programs. However, there remain at least two legitimate uses in contemporary programs: interrupting nested loops and implementing state machines.

Labels And The goto Operator

A goto statement consists of the goto keyword, followed by a programmer-created identifier, and terminated with a semicolon. A label is any unique, legal identifier followed by a colon. Programmers may label any statement in a program and "jump" to it with a goto statement. Early versions of FORTRAN and the Basic programming language had few control statements (such as the ones introduced in this chapter). So, complex program control was implemented with labels and a tangled web of goto statements, resulting in what is now derisively called "spaghetti code" because the chaotic logic reminds programmers of a bowl of spaghetti.

some_statements;
label1: statement1;
	...
if (...)
	goto label1;
else
	goto label2;
	...
label2: statement2;
while (...)
{
	...
	goto label;
	...
}

some_statements;
label: statement;
some_statements;
goto label;
...
for (int i = 0; i < 100; i++)
{
	...
	label: statement;
	...
}
(a)(b)(c)
Basic goto statements. It's easy to understand a basic goto statement: when executed, it causes program control to jump to a different location in the program. Problems arise when a program has many goto statements whose targets (the labeled statements) are scattered indiscriminately throughout a program. We began the chapter by describing a flow of control as an inked path through a program. Programs with many goto statements produce many broken, disjoint paths. These paths resemble a tangled bowl of spaghetti, where it isn't easy to see the ends or how the strands relate.
  1. goto statements can shift program control forward and backward.
  2. Jumping out of loops, forward or backward, is allowed.
  3. It is illegal to jump into a loop.
Understanding and maintaining spaghetti code is often challenging because goto statements can transfer control to arbitrary places inside statement sequences, making it difficult to know the conditions when individual statements in the sequence run and when they don't.

Exiting Nested Loops

Interrupting a loop early (i.e., before the loop-control condition ends the loop "normally") allows programmers to write efficient code (see case analysis). break and continue are sufficient to interrupt a single, unnested loop. For example:

for (int i = 0; i < counter; i++)
{
	.  .  .
	if (condition)
		break;
	.  .  .
}
for (int i = 0; i < counter; i++)
{
	.  .  .
	if (condition)
		continue;
	.  .  .
}
(a)(b)
Interrupting loops with break and continue. break and continue statements are typically placed inside an if-statement, but a switch will also work. Whenever a break or continue executes (i.e., whenever the condition is true), all of the statements between the break or continue are skipped.
  1. When the break statement operates, execution resumes with the first statement following the end of the loop.
  2. When the continue statement operates, execution resumes at the top of the loop. If the loop is a for-loop, the program executes the increment expression, then (for all loops) it reevaluates the test expression, and if it is true, the next iteration of the loop runs.

However, the break and continue statements only work for a single, unnested loop. If two or more loops are nested, break and continue only apply to the loop in which they are called (i.e., if called from a nested loop, they cannot affect an outer loop). Programmers typically use a goto statement when they need to break or continue out of nested or inner loops. The goto label merely provides a target for the goto operation: when the goto executes, the program transfers control to the statement following the label.

for (int i = 0; i < counter; i++)
{
	for (int j = 0; j < c2; j++)
	{
		.  .  .
		if (condition)
			goto done;
		.  .  .
	}
}
done:
 
int	i = 0;
next:
for (; i < counter; i++)
{
	for (int j = 0; j < c2; j++)
	{
		.  .  .
		if (condition)
			goto next;
		.  .  .
	}
}
(a)(b)
Interrupting nested loops with goto. Programmers can interrupt nested loops with a goto and label. They can effect either a break or continue by choosing where to place the label.
  1. With the label placed below the outer loop, the goto performs as a multi-level break
  2. With the label placed above the outer loop, the goto performs as a multi-level continue (note that the loop control variable must be defined and initialized above the loop; otherwise the goto would reinitialize the outer loop

Implementing State Machines

State machines are abstractions practitioners in many disciplines use to describe a system's dynamic behavior. They are most beneficial when an entity's response to an event depends on its response to previous events. Numerous state machine notations are in use, but all consist of finite sets of states and transitions. A state represents an entity's current condition or activity. A transition is a legal way the machine can change from one state to another. While the basic state machine notation is consistent across domains, variations exist. The following figure describes UML state diagrams.

The UML represents a state as a rectangle with rounded corners, a name at the top, and a transition as an arrow from one state to another. Transition labels consist of three optional elements. Aside from the name, states are usually empty but may include three optional elements.
UML states and transitions. The figure illustrates the UML state and transition syntax. Programmers typically describe a system's dynamic detail on the transition arrows, but they can also put the detail in the states in specialized situations. Programmers implement actions and activities with statements and function calls.

Example State Machine Problem

Write a program that reads a C++ program file and separates the comments from the program code. The program should write the comments to one file and the program code to another file.

UML State Diagram

A UML state diagram describing the steps to extract comments from a C++ program. The program code below is the best description of this illustration.
State machine separating comments and code. The solid disk denotes the start state, while the "bull's eye" denotes a legal end or halt state.

State Machine Solution

/* Separates comments from program code. Writes comments to one file, and the program
 * Code without comments is written to a second file.
 * Demonstration of using the goto statement to implement a state machine. Each state
 * is represented by a labeled section of code. Implements transitions with goto
 * statements.
 */


#include <iostream>
#include <fstream>
#include <string>
using namespace std;


int main()
{
	// ---------------- opens files --------------------------------------
	string	program;
	cout << "Enter the input program file name: ";
	getline(cin, program);

	string	comments;
	cout << "Enter the comments output file name: ";
	getline(cin, comments);

	string	code_file;
	cout << "Enter the output file name: ";
	getline(cin, code_file);

	ifstream	in(program);
	ofstream	comm(comments);
	ofstream	code(code_file);

	if (in.fail() || comm.fail() || code.fail())
	{
		cerr << "Unable to open a file" << endl;
		return 1;
	}

	// ---------------- state machine begins ------------------------------

	int	c;		// input character

	reading:		// start state and reading program text
		c = in.get();
		cout << "reading: " << (char)c << endl;

		switch (c)
		{
			case EOF:
				return 0;	// no more input to process
			case '/':
				goto beginning;
			default:
				code.put(c);
				goto reading;
		}


	beginning:		// possible beginning of a comment
		c = in.get();

		switch (c)
		{
			case '/':
				goto cpp_comment;
			case '*':
				goto c_comment;
			default :			// wasn't a comment
				code.put('/');
				code.put(c);
				goto reading;
		}


	cpp_comment:		// C++ style comment: // to end of line
		c = in.get();

		switch (c)
		{
			case '\n':
				comm.put(c);
				code.put(c);
				goto reading;
			default:
				comm.put(c);
				goto cpp_comment;
		}


	c_comment:		// C style comment (also supported by C++): /* to */
		c = in.get();

		switch (c)
		{
			case '*' :
				goto ending;
			default:
				comm.put(c);
				goto c_comment;
		}


	ending:			// possible end of C style comment
		c = in.get();

		switch (c)
		{
			case '/':
				comm.put('\n');
				goto reading;
			default:		// comment didn't end
				comm.put('*');
				comm.put(c);
				goto c_comment;
		}
}
A state machine implemented with goto. The program implements states as labeled blocks of code. It organizes transitions, with their actions, as cases in switch statements. Whenever programmers use goto, they assume responsibility for creating and maintaining understandable code. When implementing state machines, four "rules" help maintain understandability:
  1. Group the state-blocks together
  2. Choose descriptive state and label names
  3. Create a single transition into a block at the top
  4. Represent each event with a case and group the transition's actions and goto in it