The Department of Computer Science & Engineering
cse@buffalo

CSE 305
Programming Languages
Lecture Notes
Stuart C. Shapiro
Fall, 2003


Expressions and Assignment Statements

Precedence
Determines the order of operation in an expression such as w - x / y - z or w / x - y / z. Operators with higher precedence are done first. Parentheses are used to override precedence. Languages tend to be similar in their operator precedence order.

Associativity
Determines the order of operation between operators of the same precedence, such as x - y - z or x / y / z. Most operators in most languages are left associative, except that exponentiation (when included) is usually right associative. Parentheses are, again, used to override associativity.

A mathematical operator, •, is associative when ((x • y) • z) = (x • (y • z)), so that the order of evaluation does not matter. Addition and multiplication are mathematically associative, but may not be associative computationally. For example,

bsh % print(4.+4.+4.+4.+4.+4.+4.+4.+4.+4.+4.+3e17);
3.0000000000000006E17

bsh % print(3e17+4.+4.+4.+4.+4.+4.+4.+4.+4.+4.+4.);
3.0E17
In particular, when adding a series of floating point numbers, it is better to add them starting with the smallest terms than starting with the largest terms.

Operand Evaluation Order
The issue of operand evaluation order is in an expression such as <expression>1 • <expression>2, is <expression>1 or <expression>2 evaluated first? It will not matter unless one of the expressions has a side effect.

A side effect occurs when the evaluation of an expression causes a change to some variable, rather than only producing a value. Here's an example in Java:

public class OperandOrder {
    public static int x;

    public static int f(int y) {
	x++;
	return y;
    }

    public static void main (String[] args) {
	int result;
	x = 3;
	result = x + f(5);
	System.out.println("First evaluation = " + result);

	x = 3;
	result = f(5) + x;
	System.out.println("Second evaluation = " + result);
    } // end of main ()

}// OperandOrder

------------------------------------------------
<cirrus:Programs:1:102> javac OperandOrder.java

<cirrus:Programs:1:103> java OperandOrder
First evaluation = 8
Second evaluation = 9
"The Java programming language guarantees that the operands of operators appear to be evaluated in a specific evaluation order, namely, from left to right." [Java Language Specification Second Edition, Section 15.7]

And, here's C:

#include <stdio.h>

int x;

int f(y) 
     int y; {
  x++;
  return y;
}

int g(y) 
     int y; {
  return y;
}

int main() {
  int result;
  x = 3;
  result = g(x) + f(5);
  printf("First evaluation = %3d\n", result);

  x = 3;
  result = f(5) + g(x);
  printf("Second evaluation = %3d\n", result);

  return 0;
}

------------------------------------------------
<cirrus:Programs:1:276> operandOrder.out
First evaluation = 8
Second evaluation = 9 
Interestingly, when I did this with the expressions x + f(5) and f(5) + x, the result was 9 both times.

"Except as specified later (for the function-call (), &&, ||, ?:, and comma operators), the order of evaluation of subexpressions and the order in which side effects take place are both unspecified." [ISO C Standard, Sect. 6.5[#3]]

Optimizing compilers may change the order of operand evaluation. They sometimes allow the programmer to specify that a certain section of code should not be optimized.

Overloaded Operators
An overloaded operator is one that compiles into different procedures depending on the types of its operands, such as + compiling into one prodecure for ints, another for floats, and another for Strings.

C++ and some other languages allow the programmer to supply additional overloadings to most or all of its operators.

Type Conversions
A widening conversion converts a value from type A to type B where no two type A values convert to the same type B value (at least within reasonable bounds of range and precision), and there are type B values that no type A value converts to. An example is conversion from int to double.

A narrowing conversion converts a value from type A to type B where multiple values of type A convert to the same value of type B (even within reasonable bounds of range and precision). An example is conversion from double to int.

bsh % int i;
bsh % double x;

bsh % i = 3;
bsh % x = i;
bsh % print(x);
3.0

bsh % x = 3.5;
bsh % i = (int)x;
bsh % print(i);
3
However, here's an example where even widening doesn't work because of loss of precision:
bsh % float y;

bsh % y = 123456792;
bsh % print(y);
1.23456792E8

bsh % y = 123456793;
bsh % print(y);
1.23456792E8
Usually, if the operands of one operator are of different types, the value of one is automatically converted (coerced) to the type of the other as long as that is a widening conversion, but a narrowing conversion must be explicitly specified by a cast. Ada performs very few coercions, requiring explicit casts in most cases.

Relational and Boolean Expressions
Relational expressions test or compare values of non-Boolean types, and evaluate to Boolean types. A simple example of a relational expression is x == y. Note, however, that two floating-point values, computed differently, are seldom equal.

Boolean expressions apply a Boolean operator to Boolean values. An example is i < max && a[i] == x. Usually, Boolean operators have lower precedence than relational and arithmetic operators. However, using parentheses is often a good idea, for example, (i < max) && (a[i] == x).

A standard warning given to novice programmers is that i < j < k is not correct;
it must be written (i < j) && (j < k). Since C uses 0 and 1 for Boolean values, one can get:

#include <stdio.h>

int main() {
  int i=5, j=7, k=3;
  
  if (i < j < k) {
    printf("It's true that %d < %d < %d\n", i, j, k);
  }
  return 0;
}

-----------------------------------------------------
<cirrus:Programs:1:109> gcc -Wall boolTest.c -o boolTest.out

<cirrus:Programs:1:110> boolTest.out
It's true that 5 < 7 < 3

Trust the Boolean type! I have often seen code like

if (test) {return true;}
else {return false;}
instead of the simpler
return test;
Short-circuit evaluation is the evaluation of an expression without evaluating all its subexpressions. The text has some excellent examples of when short-circuit evaluation prevents run-time errors.

Asignment Statements
Conditional Targets
The text says that flag ? count1 : count2 = 0; is legal in C++, Java, and C#, but neither our Java compiler nor our BeanShell allowed it.

Compound Assignment Operators
In the C-based languages x •= y; means the same as x = x • y; for most operators, .

Assignment as Expression
In Common Lisp and the C-based languages the assignment statement is also an expression that evaluates to the r-value that is stored in the l-value, and the assignment operator is right associative. For example, i = j = k assigns the value of k to j and then to i.

In Perl the value of the assignment operation is the l-value of the left-hand side, even though assignment is still right-associative. The l-value is dereferenced to its r-value if the assignment expression is on a right-hand side, but if it's on a left-hand side, the l-value may be stored into. Here's an example, based on L. Wall, T. Christiansen & J. Orwant, Programming Perl, (Sebastopol, CA: O'Reilly) p. 25.

#! /util/bin/perl

$x = $y = 3;

$temp = 100;

($temp *= 9/5) += 32;

print "x = $x, y = $y, temp = $temp\n";

-----------------------------------------
<cirrus:Programs:1:123> perl assignmnt.perl
x = 3, y = 3, temp = 212

Unary Assignment Operators
These are our friends, the prefix and postfix ++ and --. The tricky issue is the postfix versions. In Java, it is clear that the semantics of x++ in any context is x fetch x x fetch 1 + store pop. Here's an example:
public class UnaryAsngmnt {

    public static void main (String[] args) {
	int x=3, y = 0;
	
	System.out.println("x = " + x);
	System.out.println("y = " + y);

	y = x + x++;
	System.out.println();
	System.out.println("y = x + x++;");
	System.out.println("x = " + x);
	System.out.println("y = " + y);

	x = x++;
	System.out.println();
	System.out.println("x = x++;");
	System.out.println("x = " + x);

    } // end of main ()
    
}// UnaryAsngmnt

--------------------------------------------------
<cirrus:Programs:1:124> javac UnaryAsngmnt.java

<cirrus:Programs:1:125> java UnaryAsngmnt
x = 3
y = 0

y = x + x++;
x = 4
y = 6

x = x++;
x = 4

In C the tricky uses of these operators are officially undefined since the order of evaluation of operands is unspecified. Let's see this in two C compilers. In cc:

#include <stdio.h>

int main() {
  int x=3, y=0;

  printf("x = %d, y = %d\n", x, y);

  y = x + x++;
  printf("\ny = x + x++;\n");
  printf("x = %d, y = %d\n", x, y);

  x = x++;
  printf("\nx = x++;\n");
  printf("x = %d, y = %d\n", x, y);

  x = x++ + x;
  printf("\nx = x++ + x;\n");
  printf("x = %d, y = %d\n", x, y);

  x = x++ + x++;
  printf("\nx = x++ + x++;\n");
  printf("x = %d, y = %d\n", x, y);

  return 0;
}
---------------------------------------------
<cirrus:Programs:1:138> cc unaryAsngmnt.c -o unaryAsngmnt.out

<cirrus:Programs:1:139> unaryAsngmnt.out
x = 3, y = 0

y = x + x++;
x = 4, y = 6

x = x++;
x = 5, y = 6

x = x++ + x;
x = 11, y = 6

x = x++ + x++;
x = 24, y = 6
and the same program, compiled by gcc:
<cirrus:Programs:1:140> gcc -Wall unaryAsngmnt.c -o unaryAsngmnt.out
unaryAsngmnt.c: In function `main':
unaryAsngmnt.c:8: warning: operation on `x' may be undefined
unaryAsngmnt.c:12: warning: operation on `x' may be undefined
unaryAsngmnt.c:16: warning: operation on `x' may be undefined
unaryAsngmnt.c:20: warning: operation on `x' may be undefined
unaryAsngmnt.c:20: warning: operation on `x' may be undefined

<cirrus:Programs:1:141> unaryAsngmnt.out
x = 3, y = 0

y = x + x++;
x = 4, y = 6

x = x++;
x = 5, y = 6

x = x++ + x;
x = 6, y = 6

x = x++ + x++;
x = 7, y = 6

Assignment Operand Order
The issue of operand order also applies to the operands of the assignment operator: which is evaluated first, the left-hand side or the right-hand side? To tell, we need to consider side-effects. In Java the left-to-right operand evaluation order still applies:
public class AsngmntOrder {

    public static void main (String[] args) {
	int i=0, a[] = {5, 5, 5};
	
	System.out.println("i = " + i);
	System.out.println("a = [" + a[0]
			   + ", " + a[1]
			   + ", " + a[2] + "]");

	a[i] = i++;
	System.out.println();
	System.out.println("a[i] = i++;");
	System.out.println("i = " + i);
	System.out.println("a = [" + a[0]
			   + ", " + a[1]
			   + ", " + a[2] + "]");

	a[i++] = i;
	System.out.println();
	System.out.println("a[i++] = i;");
	System.out.println("i = " + i);
	System.out.println("a = [" + a[0]
			   + ", " + a[1]
			   + ", " + a[2] + "]");
    } // end of main ()
    
}// AsngmntOrder
------------------------------------------------
<cirrus:Programs:1:129> javac AsngmntOrder.java

<cirrus:Programs:1:130> java AsngmntOrder
i = 0
a = [5, 5, 5]

a[i] = i++;
i = 1
a = [0, 5, 5]

a[i++] = i;
i = 2
a = [0, 2, 5]

C leaves this case officially undefined,

          This paragraph renders  undefined  statement  expressions
          such as
                  i = ++i + 1;
                  a[i++] = i;                                       |
          while allowing
                  i = i + 1;
                  a[i] = i;    [ISO CStandard, Sect. 6.5(#58)]

Parallel Assignment
A statement like x = y = ... = <exp> lets you assign the same value to multiple variables, but usually the assignment of multiple values to multiple variables requires multiple assignment statements in sequential order.

Common Lisp's assignment "statement" allows multiple values to be stored in multiple variables in sequential order:

cl-user(17): (setf x 1 y 2 z 3)
3

cl-user(18): x
1

cl-user(19): y
2

cl-user(20): z
3
There is also a parallel version that allows multiple values to be stored in multiple variables in parallel order. This allows for the swapping of two variables without using an explicit temporary:
cl-user(21): x
1

cl-user(22): y
2

cl-user(23): (psetf x y y x)
nil

cl-user(24): x
2

cl-user(25): y
1

Perl also allows parallel assignment using its array (list) environment:

#! /util/bin/perl

$x = 3;
$y = 5;

print "First, x = $x, y = $y\n\n";

($x, $y) = ($y, $x);

print "Afterwards, x = $x, y = $y\n";
---------------------------------------
<cirrus:Programs:1:147> perl parallelasgnmnt.perl
First, x = 3, y = 5

Afterwards, x = 5, y = 3

First Previous Next

Copyright © 2003 by Stuart C. Shapiro. All rights reserved.

Stuart C. Shapiro <shapiro@cse.buffalo.edu>