PROGRAMMING LANGUAGES
2.1.3 Initialization
Because they already provide a construct (the assignment statement) to set the value of a variable,
imperative languages do not always provide a means of specifying an initial value for a variable in its
declaration. There are at least three reasons, however, why such initial values may be useful:
As suggested in Figure 3.3 (page 124), a static variable that is local to a subroutine needs an initial
value in order to be useful.
For any statically allocated variable, an initial value that is specified in the declaration can be pre
allocated in global memory by the compiler, avoiding the cost of assigning an initial value at run
time.
Accidental use of an uninitialized variable is one of the most common programming errors. One
of the easiest ways to prevent such errors (or at least ensure that erroneous behavior is
repeatable) is to give every variable a value when it is first declared. Most languages allow
variables of built-in types to be initialized in their declarations. A more complete and orthogonal
approach to initialization requires a notation for aggregates: built-up structured values of userdefined composite types.
Dynamic Checks
Instead of giving every uninitialized variable a default value, a language or implementation can choose to
define the use of an uninitialized variable as a dynamic semantic error, and can catch these errors at run
time.
Definite Assignment
For local variables of methods, Java and C# define a notion of definite assignment that precludes the
use of uninitialized variables. This notion is based on the control flow of the program and can be
statically checked by the compiler. Roughly speaking, every possible control path to an expression must
assign a value to every variable in that expression. This is a conservative rule; it can sometimes prohibit
programs that would never actually use an uninitialized variable. In Java:
int i;
int j = 3;
...
if (j > 0) {
i = 2;
}
... // no assignments to j in here
if (j > 0) {
System.out.println(i); // error: "i might not have been initialized"
}
While a human being might reason that i will be used only when it has previously been given a value, it is
uncomputable to make such determinations in the general case, and the compiler does not attempt it.
Department of CSE/ISE NIT,Raichur
PROGRAMMING LANGUAGES
Constructors
Many object-oriented languages (Java and C# among them) allowthe programmer to define types for
which initialization of dynamically allocated variables occurs automatically, even when no initial value is
specified in the declaration.
2.1.4 Ordering within Expressions
While precedence and associativity rules define the order in which binary infix operators are applied
within an expression, they do not specify the order in which the operands of a given operator are
evaluated. For example, in the expression
a - f(b) - c * d
we know from associativity that f(b) will be subtracted from a before performing the second subtraction,
and we know from precedence that the right operand of that second subtraction will be the result of c *
d, rather than merely c, but without additional information we do not know whether a - f(b) will be
evaluated before or after c * d. Similarly, in a subroutine call with multiple arguments
f(a, g(b), h(c))
we do not know the order in which the arguments will be evaluated. _ There are two main reasons why
the order can be important:
Side effects: If f(b) may modify d, then the value of a - f(b) - c * d will depend on whether the
first subtraction or the multiplication is performed first. Similarly, if g(b) may modify a and/or c,
then the values passed to f(a, g(b), h(c)) will depend on the order in which the arguments are
evaluated. _
Code improvement: The order of evaluation of sub-expressions has an impact on both register
allocation and instruction scheduling. In the expression a * b + f(c), it is probably desirable to call
f before evaluating a * b, because the product, if calculated first, would need to be saved during
the call to f, and f might want to use all the registers in which it might easily be saved. In a similar
vein, consider the sequence
a := B[i];
c := a * 2 + d * 3;
Here it is probably desirable to evaluate d * 3 before evaluating a * 2, because the previous
statement, a := B[i], will need to load a value from memory.
Applying Mathematical Identities
Some language implementations (e.g., for dialects of Fortran) allow the compiler to rearrange
expressions involving operators whose mathematical abstractions are commutative, associative, and/or
distributive, in order to generate faster code. Consider the following Fortran fragment:
a=b+c
d=c+e+b
Some compilers will rearrange this as
a=b+c
d=b+c+e
They can then recognize the common sub expression in the first and second statements, and generate
code equivalent to
a=b+c
d=a+e
Similarly,
Department of CSE/ISE NIT,Raichur
PROGRAMMING LANGUAGES
a = b/c/d
e = f/d/c
may be rearranged as
t=c*d
a = b/t
e = f/t _
Unfortunately, while mathematical arithmetic obeys a variety of commutative, associative, and
distributive laws, computer arithmetic is not as orderly. The problem is that numbers in a computer are
of limited precision. Suppose a, b, and c are all integers between 2 billion and 3 billion. With 32-bit
arithmetic, the expression b - c + d can be evaluated safely left-to-right (232 is a little less than 4.3 billion).
If the compiler attempts to reorganize this expression as b + d - c, however (e.g., in order to delay its use
of c), then arithmetic overflow will occur.
2.1.5 Short Circuit Evaluation
Boolean expressions provide a special and important opportunity for code improvement and increased
readability. Consider the expression (a < b) and (b < c). If a is greater than b, there is really no point in
checking to see whether b is less than c; we know the overall expression must be false. Similarly, in the
expression (a > b) or (b > c), if a is indeed greater than b there is no point in checking to see whether b is
greater than c; we know the overall expression must be true. A compiler that performs short-circuit
evaluation of Boolean expressions will generate code that skips the second half of both of these
computations when the overall value can be determined from the first half. _ Short-circuit evaluation can
save significant amounts of time in certain situations:
if (very_unlikely_condition && very_expensive_function()) ... _
But time is not the only consideration, or even the most important. Short- circuiting changes the
semantics of Boolean expressions. In C, for example, one can use the following code to search for an
element in a list:
p = my_list;
while (p && p->key != val)
p = p->next;
C short-circuits its && and || operators, and uses zero for both null and false, so p->key will be accessed if
and only if p is non-null. The syntactically similar code in Pascal does not work, because Pascal does not
short-circuit and and or:
p := my_list;
while (p <> nil) and (pˆ.key <> val) do (* ouch! *)
p := pˆ.next;
Here both of the <> relations will be evaluated before and-ing their results together. At the end of an
unsuccessful search, p will be nil, and the attempt to access pˆ.key will be a run-time (dynamic semantic)
error, which the compiler may or may not have generated code to catch. To avoid this situation, the
Pascal programmer must introduce an auxiliary Boolean variable and an extra level of nesting:
p := my_list;
still_searching := true;
while still_searching do
if p = nil then
Department of CSE/ISE NIT,Raichur
PROGRAMMING LANGUAGES
still_searching := false
else if pˆ.key = val then
still_searching := false
else
p := pˆ.next;
Short-circuit evaluation can also be used to avoid out-of-bound subscripts:
const MAX = 10;
int A[MAX]; /* indices from 0 to 9 */
...
if (i >= 0 && i < MAX && A[i] > foo) ...
division by zero:
if (d <> 0 && n/d > threshold) ...
and various other errors. _
Short circuiting is not necessarily as attractive for situations in which a Boolean sub-expression can cause
a side effect.
found_it := p /= null and then p.key = val;
...
if d = 0 or else n/d < threshold then ...
(Ada uses /= for “not equal.”) In C, the bit-wise & and | operators can be used as non–short-circuiting
alternatives to && and || when their arguments are logical
(0 or 1) values. _
If we think of and and or as binary operators, short circuiting can be considered an example of delayed or
lazy evaluation: the operands are “passed” unevaluated.
Department of CSE/ISE NIT,Raichur
© Copyright 2026 Paperzz