In computer science and in computer programming,
statements in pseudocode or in a program
are normally obeyed one after the other in the order in which they are written
(sequential flow of control).
Most programming languages have control flow statements which allow
variations in this sequential order:
statements may only be obeyed under certain conditions (choice),
statements may be obeyed repeatedly (loops),
a group of remote statements may be obeyed (subroutines).
The use of subroutines does not normally cause any control flow problems,
but see the discussions below on early return, error recovery, and labels as parameters.
At the machine/assembly language level, it is usually the case that the only instructions available for handling choice and/or loops are goto and conditional goto (often known as variations of jump and/or branch). Compilers for high-level programming languages must translate all control-flow statements into these primitives.
In many programming languages, a label is an identifier,
which is attached to a statement by using a colon (:), e.g.
Success:print("target has been found")
Historical note: Algol 60 allowed both whole numbers and identifiers as labels
(both attached by colons to statements),
but few if any implementations allowed whole numbers.
A number of authors have pointed out that using goto is often acceptable,
provided that control is transferred to some later statement (forward jump)
and that control is not transferred into the middle of some other structured statement.
Some of the control-flow statements available in high-level programming languages
are effectively disguised gotos which comply with these conditions,
e.g. break, continue, return as found in C/C++.
The terminology for subroutines varies;
they may alternatively be known as routines, procedures, or sometimes methods.
If they can be used in an expression and return a single result,
they may also be known as functions.
In the 1950's, computer memories were very small by current standards
so subroutines were used primarily to reduce program size;
a piece of code was written once and then used many times
from various other places in the program.
Nowadays, subroutines are more fequently used to help make a program more structured,
e.g. by isolating some particular algorithm or hiding some particular data access method.
If many programmers are working on a single program,
subroutines can be used to help split up the work.
Subroutines can be made much more useful by providing them with parameters,
e.g. many programming langauges have a built-in square root subroutine
whose parameter is the number you wish to find the square root of.
Some programming languages allow recursion,
i.e. subroutines can call themselves directly or indirectly.
Certain algorithms such as Quicksort and various tree-traversals are
very much easier to express in recursive form than in non-recursive form.
The use of subroutines does slow a program down slightly,
due to the overhead of passing parameters, calling the subroutine,
entering the subroutine (may involve saving information on a stack), and returning.
The actual overhead depends on both the hardware instructions available
and on any software conventions which are used;
excluding parameters, the overhead may range from 2 to 14 instructions, or worse.
Some compilers may effectively insert the code of a subroutine
inline at the point of call
to remove this overhead.
In some programming languages, the only way of returning from a subroutine is
by reaching the physical end of the subroutine.
Other languages have a return statement.
This is equivalent to a forward jump to the physical end of the subroutine
and so does not complicate the control flow situation.
There may be several such statements within a subroutine if required.
In most cases, a call to a subroutine is only a temporary diversion
to the sequential flow of control, and so causes no problems for control flow analysis.
A few languages allow labels to be passed as parameters,
in which case understanding the control flow becomes very much more complicated,
since you may then need to understand the subroutine to figure out what might happen.
No final keyword: Algol 60,
Pascal, C,
C++, Java,
PL/1.
Such languages need some way of grouping statements together,
e.g. beginend for Algol 60 and Pascal,
curly brackets { } for C, C++, Java.
Final keyword: Algol 68, Modula-2, Fortran (77 onwards).
The forms of the final keyword vary:
Algol 68: initial keyword backwards e.g. iffi, caseesac,
Modula-2: same final keyword end for everything (now thought not to be good idea),
Fortran 77: final keyword is end + initial keyword, IF ENDIF, DO ENDDO
Languages which have a final keyword tend to have less debate
regarding layout and indentation.
Languages whose final keyword is of the form: end + initial keyword
tend to easier to learn.
PL/1 has some 22 standard conditions
(e.g. ZERODIVIDE SUBSCRIPTRANGE ENDFILE)
which can be RAISEd and which can be intercepted by: ON condition action;
Programmers can also define and use their own named conditions.
In many cases a GOTO is needed to decide where flow of control should resume.
Unfortunately, some implementations had a substantial overhead in both space and time
(especially SUBSCRIPTRANGE), so many programmers tried to avoid using conditions.
C++, Java, and C# have a special construct for exception handling:
try {
xxx1 // Somewhere in here
xxx2 // use: throw someValue;
xxx3
} catch (someClass & someId) { // catch value of someClass
actionForSomeClass
} catch (someType & anotherId) { // catch value of someType
actionForSomeType
} catch (...) { // catch anything not already caught
actionForAnythingElse
}
Any number and variety of catch clauses can be used above. In Java and C#, a finally
clause can be added to the try construct. No matter how control leaves the try
the code inside the finally clause is guaranteed to execute. This is useful when
writing code that must relinquish an expensive resource (such as an opened file
or a database connection) when finished processing:
FileStream stm = null; // C# example
try {
stm = new FileStream("logfile.txt", FileMode.Create);
return ProcessStuff(stm); // may throw an exception
} finally {
if (stm != null)
stm.Close();
}
Since this pattern is fairly common, C# has a special syntax that is slightly more readable:
using (FileStream stm = new FileStream("logfile.txt", FileMode.Create)) {
return ProcessStuff(stm); // may throw an exception
}
Upon leaving the using-block, the compiler guarantees that the stm object is released.
The C++, Java, and C# languages define standard exceptions and the circumstances under which they are thrown.
Users can throw exceptions of their own (in fact C++ allows users to throw and catch almost any type!)
If there is no catch matching a particular throw, then control percolates back through subroutine calls and/or nested blocks until a matching catch is found or until the end of the main program is reached, at which point the program is forcibly stopped with a suitable error message.
Can anyone add something helpful about these or other programming languages?
For reasons of backwards compatibility,
Fortran still has some arcane unstructured features which should be avoided,
e.g. ASSIGNed GOTO, Arithmetic IF (3-way branch),
Logical IF (2-way branch), labels as parameters.
Self-modifying code, i.e. code which alters itself when executing,
tends to result in very obscure code.
Most assembly languages allow this, as does the ALTER verb in COBOL.
In a spoof Datamationarticle (December 1973),
R. Lawrence Clark suggested that the GOTO statement
could be replaced by the COMEFROM statement,
and provides some entertaining examples.
This was actually implemented in the INTERCAL programming language,
a language designed to make programs as obscure as possible.
In his 1974 article,
Donald Knuth identified two situations which were not covered
by the control structures listed above,
and gave examples of control structures which could handle these situations.
Despite their utility, these constructions have not yet found their way
into main-stream programming languages.
If xxx1 is omitted we get a loop with the test at the top.
If xxx2 is omitted we get a loop with the test at the bottom.
If while is omitted we get an infinite loop.
Hence this single construction can replace several constructions in most programming languages.
A possible variant is to allow more than one while test; within the loop,
but the use of exitwhen (see next section) appears to cover this case better.
As the example on the right shows (copying a file one character at a time),
there are simple situations where this is exactly the right construction to use
in order to avoid duplicated code and/or repeated tests.