Sequencers
In this chapter we study several kinds of sequencer:
Jumps
if E then C1 else C2
-------------------
| |
| true |
------> E ---> C1 ---------->
| | | |
| -----> C2 - |
| false |
| |
-------------------
while E do C
----------------------
| |
| false |
---------> E ------------------>
| | | |
| | ------> C |
| | true | |
| <----------- |
----------------------
It is ``easy'' to follow the flow-of-control when these structures are
``plugged-together''.
While all structured control constructs
can be implemented with a disciplined use of gotos, undisciplined use of
goto's make reasoning about programs more difficult
because they allow multiple entry/exit from program blocks.
Jump appears in many programming languages, typically in the form "goto label" .
Consider the following program fagment:
begin
if E1 then C1
else begin C2;
goto L
end;
while E2 do
begin
C4;
L: C5:
end
end
A jump might be a forward jump that causes commands to be skipped, or backward jump that causes commands to be repeated.
Unrestricted jumps might give rise to 'spaghetti' programs which are hard to understand.
The jump introduce unwanted complexity into the semantic of a high-level language.
Wise programmer avoid using jumps in complicated ways.
while (not eof()) {
if (ch == 'q') break;
}
The control flow diagram is the following.
----------------------
| |
| false |
---------> E ------------------>
| | | | |
| | ------> C--------
| | true | |
| <----------- |
----------------------
The C break statement can only exit the containing block, so if we had two nested loop with the break in the inner loop, it couldn't exit from both loops.
In a function, a return can act as a general escape (leave the function now).
while (...) {
while (...) {
if (ch == 'q') return; /* exit both loops */
}
}
The most drastic of all is to ``halt'' the entire program. In C, we can do this with an exit() function call.
while (...) {
while (...) {
if (error) exit(return_code); /* exit program */
}
}
Exceptions
An exception is an ``exceptional'' or unexpected condition. Common exceptions are
arithmetic, e.g., division-by-zero
arrays, e.g., out-of-bounds
I/O, e.g., can't open file
When the condition happens, an exception is raised or signaled, there are machine code instructions that raise the exception. Some languages have no capabilities for handling exceptions. What happens when we try to divide by zero in Pascal? In C?
x = 0; z = y / x; /* error division by zero --> core dump */
Other languages provide exception handlers. PL/1 was the first major language to provide exception handlers.
For example, an application program attempts to read a nonexistent record from a file. This exceptional condition would be detected in the filestore software, but the handler should be in the program itself.
Java provides exception handlers with the try-catch statement.
try {
x = 0;
z = y / x;
x = z; // control returns to this statement after the exception.
} catch (dividebyzero) {
System.out.println("Warning, tried to divide-by-zero,am continuing");
z = 0;
}
Exceptions are especially nice in I/O. Consider the standard act of opening a file and reading data into memory. There are lots of exceptions (or errors) to handle.
readFile {
try {
open the file;
determine its size;
allocate that much memory;
read the file into memory;
close the file;
} catch (fileOpenFailed) {
doSomething;
} catch (sizeDeterminationFailed) {
doSomething;
} catch (memoryAllocationFailed) {
doSomething;
} catch (readFailed) {
doSomething;
} catch (fileCloseFailed) {
doSomething;
}
}
Note that the catch statements scans for particular kinds of errors. The alternative is to write code like this.
errorCodeType readFile {
initialize errorCode = 0;
open the file;
if(theFileIsOpen) {
determine the length of the file;
if (gotTheFileLength) {
allocate that much memory;
if (gotEnoughMemory) {
read the file into memory;
if (readFailed) {
errorCode = -1;
}
} else {
errorCode = -2;
}
} else {
errorCode = -3;
}
close the file;
if(theFileDidntClose &&errorCode ==0)
{
errorCode = -4;
} else {
errorCode = errorCode and -4;
}
} else {
errorCode = -5;
}
return errorCode;
}
But this code is very bloated, and it is unclear as to what is actually going on.
What happens if we call a method inside of a try statement, but detect errors in that method? A method may propagate exceptions to callees (up the call stack).
method1 {
try {
call method2;
} catch (exception) {
doErrorProcessing;
}
}
method2 throws exception {
call method3;
}
method3 throws exception {
call readFile;
}
Exception handling in Java
Java Exception classes hierarchy, whose root class is java.lang.Exception, is divided into two groups of classes which are treated differently by the Java language. Firstly there is the java.lang. Runtime Exception class and all the classes which are derived from it; and secondly all other classes descended from java.lang.Exception.
Object
|--Throwable
|--Exception
| |--..
|--RuntimeException --|--..
| |--..
|--io.IOException
|--EOFException
|--FileNotFoundException
|--InterruptedIOException
|--ObjectStreamException
|--java.rmi.RemoteException..
All exceptions are 'objects' generated from one of above classes, and thrown
from an error or exception source. The exception handling program must
'catch' these exception objects to process them.
try-catch-finally statement
try {
statements;
//Normal program, possibly generate exception
}
catch(Throwable-subclass e){
statements; // exception handling codes
}
....
finally{
statements;
//these will always be executed.
}
It is possible to have several catch sub-statement. Sequentially look for the next to use, until exception type matches (consistent) The exception is 'thrown' out to the caller, if the called program cannot catch the error. If user does not catch the error, then Java exception handler catches it, and stops the program.
Example
class BadString {
public static void main(String args[]){
String name=null;
try{
if (name.equals("John"))
System.out.println("Name is "+name);
}
catch(ArithmeticException e){
System.out.println("ArithmeticException!");
}
catch(NullPointerException e){
System.out.println("NullPointerException!");
}
}
}
The result: NullPointerException!
Can also replace the 2nd catch statement by:
catch(Exception e) { System.out ...}
What's the problem here?
Consider program segment:
try {
do something;
}
catch(Exception e) {}
catch(NullPointerException){}
\\not reachable
Java compiler tells you this is wrong.
Another Example
class Example{
public static void main(String args[]){
try{
int x=0;
int y=20;
int z=y/x;
System.out.println("The value of y/x is: "+z);
}
catch(ArithmeticException e){
System.out.println("Caught ArithmeticException");
}
finally
{
System.out.println("Executing finally clause.");
try{
String name=null;
if(name.equals("Tom")){
System.out.println("My name is Tom.");}
}
catch(Exception e){
System.out.println("Caught another exception");
}
finally{
System.out.println("Executing finally clause again.");
}
}
}
} // will trace this in class
Use finally together with break/continue/return
class example{
public static void main(String args[]){
while(true){
try{
System.out.println("Want to leave the try block");
break;
}
finally{
System.out.println("Must do this before leaving");
}
}
}
}
Output: Want to leave the try block
Must do this before leaving
Similarly, for continue and return.