3.12. Supplemental: Legitimate Uses For The goto Statement

goto statements are not typically covered in introductory courses and you may skip this section if it was not explicitly assigned.

The goto statement is rarely used anymore and therefore rarely covered. It is, deservedly, much-maligned. Its unrestrained use has resulted in too many incomprehensible and unmaintainable programs. However, there remain at least two legitimate uses in contemporary programming languages like C++: interrupting nested loops and implementing state machines.

Labels And The goto Operator

A label is any unique, legal identifier followed by a colon. A goto statement is the goto keyword followed by a label and terminated with a semicolon. Programmers may label any statement in a program, and they may arbitrarily "jump" to the labeled statement with a goto statement. Early versions of FORTRAN and the Basic programming language had if-statements but not loops. So, program control was implemented by a tangled web of goto statements. The resulting programs were often described as "spaghetti code" because the logic resembled a tangled bowl of spaghetti.

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

label: statement;
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. 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 to each other.
  1. goto statements can shift program control forward and backward.
  2. Jumping out of loops, forwards or backwards, is allowed.
  3. It is illegal to jump into a loop.

Exiting Nested Loops

Either break or 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)
break and continue 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 increment expression is executed, then (for all loops) the test is evaluated and if it is true, the next iteration of the loop takes place.

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 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, control is transfered 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)
A goto and a label can be used to effect a multi-level break or continue operation. The distinction is where the label statement is placed.
  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

A state machine is an abstraction used in many disciplines to describe the dynamic behavior of some component implemented in software or hardware (or both). They are composed of finite sets of states and transitions. A state represents the entity's current condition or activity. A transition is a legal way that the machine can change from one state to another. Events, such a reading an input, trigger transitions. Programmers can add detail to a transition with three optional features delimited by three symbols: [, ], and /.

event [ guard condition ] / action
event
An event is something that happens: a setting that changes, a value that is read in, a button is pressed, etc. Events are optional, and transitions without a triggering event are called null or lambda transitions. Null transitions permit a state change as soon as control enters the state if the guard condition permits the change.
[ guard condition ]
A guard condition, denoted by the square brackets, is a Boolean-valued expression based on an object's attributes and operations. It behaves just like an if-statement. If a transition has a guard condition, that condition must be true before the transition is allowed. If an event, including null events, triggers two transitions, then the guard conditions must be exclusive (i.e., no two conditions may be true simultaneously).
/ action
An action is an operation (i.e., something that an object can do) and is denoted by a forward slash character preceding the action name.

State machines are most useful in cases where a component's response to an event depends on how it responded to previous events. There are numerous state machine notations in use. The UML state diagram represents each state as a rectangle with rounded corners and each transition as a labeled arrow from a source to a destination state.

A state represented as a rectangle with rounded cornners. The State name is at the top. This state has entry and exit actions and a do activity: entry / action1, exit / action2, and do / activity.
A UML state. Individual states may also specify behavioral detail. Three optional behaviors are specified by three key words:
entry
A behavior or action that takes place as soon as the state is entered
exit
A behavior or action that takes place the last thing as a state is exited
do
An ongoing behavior or activity that begins when the state is entered and continues until the state is exited

Example 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 actually the best description of this illustration.
State machine for separating comments from code. The states and transitions are as described above. Additionally, the solid disk denotes the start state while the "bulls eye" denotes a legal end or halt state.

State Machine Solution

/* Separates comments from program code. Comments are written to one file, and 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. Transitions are implemented 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 program code 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;
		}
}