CS13002 Programming and Data Structures

Spring semester

Assignments

Assignments and imperative programming

Initialization during declaration helps one store constant values in memory allocated to variables. Later one typically does a sequence of the following:

This three-stage process is effected by an assignment operation. A generic assignment operation looks like:
   variable = expression;

Here expression consists of variables and constants combined using arithmetic and logical operators. The equality sign (=) is the assignment operator. To the left of this operator resides the name of a variable. All the variables present in expression are loaded to the CPU. The ALU then evaluates the expression on these values. The final result is stored in the location allocated to variable. The semicolon at the end is mandatory and denotes that the particular statement is over. It is a statement delimiter, not a statement separator.

Animation example : expression evaluation

A C program typically consists of a sequence of statements. They are executed one-by-one from top to bottom (unless some explicit jump instruction or function call is encountered). This sequential execution of statements gives C a distinctive imperative flavor. This means that the sequence in which statements are executed decides the final values stored in variables. Let us illustrate this using an example:

   int x = 43, y = 15;   /* Two integer variables are declared and initialized */

   x = y + 5;  /* The value 15 of y is fetched and added to 5.
                  The sum 20 is stored in the memory location for x. */
   y = x;      /* The value stored in x, i.e., 20 is fetched and stored
                  back in y. */

After these statements are executed both the memory locations for x and y store the integer value 20.

Let us now switch the two assignment operations.

   int x = 43, y = 15;   /* Two integer variables are declared and initialized */

   y = x;      /* The value stored in x, i.e., 43 is fetched and stored
                  back in y. */
   x = y + 5;  /* The value 43 of y is fetched and added to 5.
                  The sum 48 is stored in the memory location for x. */

For this sequence, x stores the value 48 and y the value 43, after the two assignment statements are executed.

The right side of an assignment operation may contain multiple occurrences of the same variable. For each such occurrence the same value stored in the variable is substituted. Moreover, the variable in the left side of the assignment operator may appear in the right side too. In that case, each occurrence in the right side refers to the older (pre-assignment) value of the variable. After the expression is evaluated, the value of the variable is updated by the result of the evaluation. For example, consider the following:

  int x = 5;

  x = x + (x * x);

The value 5 stored in x is substituted for each occurrence of x in the right side, i.e., the expression 5 + (5 * 5) is evaluated. The result is 30 and is stored back to x. Thus, this assignment operation causes the value of x to change from 5 to 30. The equality sign in the assignment statement is not a mathematical equality, i.e., the above statement does not refer to the equation x = x + x2 (which happens to have a single root, namely x = 0). It similarly makes sense to write

   z = z + 2;

to imply an assignment (increment the value of z by 2). Mathematically, it makes little sense, since no numbers you know seem to satisfy the equation z = z + 2. (But I know some of them!) Notice that in C there is a different way for checking equality of two expressions. The single equality sign is not that.

Floating point numbers, characters and array locations may also be used in assignment operations.

   float a = 2.3456, b = 6.5432, c[5];  /* Declare float variables and arrays */
   char d, e[4];                        /* Declare character variables and arrays */

   c[0] = a + b;         /* c[0] is assigned 2.3456 + 6.5432, i.e., 8.8888 */
   c[1] = a - c[0];      /* c[1] is assigned 2.3456 - 8.8888, i.e., -6.5432 */
   c[2] = b - c[0];      /* c[2] is assigned 6.5432 - 8.8888, i.e., -2.3456 */
   a = c[1] + c[2];      /* a is assigned (-6.5432) + (-2.3456), i.e., -8.8888 */

   d = 'A' - 1;          /* d is assigned the character ('@') one less than 'A' in the ASCII chart */
   e[0] = d + 1;         /* e[0] is assigned the character next to '@', i.e., 'A' */
   e[1] = e[0] + 1;      /* e[1] is assigned the character next to 'A', i.e., 'B' */
   e[2] = e[0] + 2;      /* e[2] is assigned the character second next to 'A', i.e., 'C' */
   e[3] = e[2] + 1;      /* e[3] is assigned the character next to 'C', i.e., 'D' */

An assignment does an implicit type conversion, if its left side turns out to be of a different data type than the type of the expression evaluated.

   float a = 7.89, b = 3.21;
   int c;

   c = a + b;

Here the right side involves the floating point operation 7.89 + 3.21. The result is the floating point value 11.1. The assignment plans to store this result in an integer variable. The value 11.1 is first truncated and subsequently the integer value 11 is stored in c. One can explicitly mention this typecasting command as:

   float a = 7.89, b = 3.21;
   int c;

   c = (int)(a + b);

The parentheses around the expression a + b implies that the typecasting is to be done after the evaluation of the expression. The following variant has a different effect:

   float a = 7.89, b = 3.21;
   int c;

   c = (int)a + b;

Here a is first converted to 7 and then added to 3.21. The resulting value (10.21) is truncated and stored in c. That is, now c is assigned the value 10.

In C, an assignment operation also returns a value. It is precisely the value that is assigned. This value can again be used in an expression.

   int a, b, c;

   c = (a = 8) + (b = 13);

Here a is assigned the value 8 and b the value 13. The values (8 and 13) returned by these assignments are then added and the sum 21 is stored in c. The assignment of c also returns a value, i.e., 21. Here we have ignored this value. Assignment is right associative. For example,

   a = b = c = 0;

is equivalent to

   a = (b = (c = 0));

Here c is first assigned the value 0. This value is returned to assign b, i.e., b also gets the value 0. The value returned from this second assignment is then assigned to a. Thus after this statement all of a, b and c are assigned the value 0.

Built-in operators

Now that we know how to assign values to variables, what remains is a discussion on how expressions can be generated. Here are the rules:

These rules do not exhaust all possibilities for generating expressions, but form a handy set to start with.

Examples:

   53                  /* constant */
   -3.21               /* constant */
   'a'                 /* constant */
   x                   /* variable */
   -x[0]               /* unary negation on a variable */
   x + 5               /* addition of two subexpressions */
   (x + 5)             /* parenthesized expression */
   (x) + (((5)))       /* another parenthesized expression */
   y[78] / (x + 5)     /* more complex expression */
   y[78] / x + 5       /* another complex expression */
   y / (x = 5)         /* expression involving assignment */
   1 + 32.5 / 'a'      /* expression involving different data types */

Non-examples:

   5 3                 /* space is not an operator and integer constants may not contain spaces */
   y *+ 5              /* *+ is not a defined operator */
   x (+ 5)             /* badly placed parentheses */
   x = 5;              /* semi-colons are not allowed in expressions */

We now list the basic operators defined in C and the interpretations of these operators.

Arithmetic operators

Arithmetic operators include negation, addition, subtraction, multiplication and division. The result of the operation depends on which type of data the arithmetic operator operates on. The following table summarizes the relevant information.

OperatorMeaningDescription
-unary
negation

Applicable for integers and real numbers. Does not make enough sense for unsigned operands.

+(binary)
addition

Applicable for integers and real numbers.

-(binary)
subtraction

Applicable for integers and real numbers.

*(binary)
multiplication

Applicable for integers and real numbers.

/(binary)
division

For integers division means "quotient", whereas for real numbers division means "real division". If both the operands are integers, the integer quotient is calculated, whereas if (one or both) the operands are real numbers, real division is carried out.

%(binary)
remainder

Applicable only for integer operands.

Examples: Here are examples of integer arithmetic:

   55 + 21 evaluates to 76.
   55 - 21 evaluates to 34.
   55 * 21 evaluates to 1155.
   55 / 21 evaluates to 2.
   55 % 21 evaluates to 13.

Here are some examples of floating point arithmetic:

   55.0 + 21.0 evaluates to 76.0.
   55.0 - 21.0 evaluates to 34.0.
   55.0 * 21.0 evaluates to 1155.0.
   55.0 / 21.0 evaluates to 2.6190476 (approximately).
   55.0 % 21.0 is not defined.

Note: C does not provide a built-in exponentiation operator.

Bitwise operators

Bitwise operations apply to unsigned integer operands and work on each individual bit. Bitwise operations on signed integers give results that depend on the compiler used, and so are not recommended in good programs. The following table summarizes the bitwise operations. For illustration we use two unsigned char operands a and b. We assume that a stores the value 237 = (11101101)2 and that b stores the value 174 = (10101110)2.

OperatorMeaningExample
& AND
a = 23711101101
b = 17410101110
a & b is 17210101100
| OR
a = 23711101101
b = 17410101110
a | b is 23911101111
^ EXOR
a = 23711101101
b = 17410101110
a ^ b is 67 01000011
~ Complement
a = 23711101101
  ~a is 18  00010010
>> Right-shift
a = 23711101101
a >> 2 is 5900111011
<< Left-shift
b = 17410101110
b << 1 is 9201011100

Some shorthand notations

C provides some shorthand notations for some particular kinds of operations. For example, if the variable to be assigned is the first operand in the expression on the right side, then this variable may be omitted in the expression and the operator comes before the equality sign. More precisely, the assignment

   var = var op expression;

is equivalent to

   var op= expression;

Here the operator op can be any binary operator described above, namely, +,-,*,/,%,&,|,^,>>,<<. Some specific examples are:

   a = a + 10.43;        is equivalent to   a += 10.43;
   a = a % 43;           is equivalent to   a %= 43;
   c = c * (a + b - c);  is equivalent to   c *= a + b - c;
   a = a >> 3;           is equivalent to   a >>= 3;
   b = b ^ (a << 3);     is equivalent to   b ^= (a << 3);

A special case of this can be shortened further: increment/decrement by 1.

   a = a + 1; is equivalent to a += 1; which is also equivalent to ++a;
   b = b - 1; is equivalent to b -= 1; which is also equivalent to --b;

These increment/decrement operators (++ and --) are called pre-increment and pre-decrement operators. C also provides post-increment and post-decrement operators. These operators are same (++ and --) but are written after the variable being incremented/decremented. The isolated statements

   a++;
   b--;

are respectively equivalent to

   ++a;
   --b;

However, there is a subtle difference between the two. Recall that every assignment returns a value. The increment (or decrement) expressions ++a and a++ are also assignment expressions. Both stand for "increment the value of a by 1". But then which value of a is returned by this expression? We have the following rules:

A similar argument holds for the decrement operations. The following examples illustrate the differences:

   a = 43;
   b = 15;
   c = (++a) * (--b);

Here a is first incremented and the value 44 is returned. Also b is decremented and the value 14 is returned. Then these two values are multiplied and the product 44*14 = 616 is assigned to c.

   a = 43;
   b = 15;
   c = (++a) * (b--);

Now a is first incremented and the value 44 is returned. But the value of b is first returned (15) and then decremented. Thus c gets the value 44*15 = 660. Similarly, after the execution of the following statements

   a = 43;
   b = 15;
   c = (a++) * (b--);
a, b and c respectively hold the values 44, 14 and 43*15 = 645.

Precedence of operators

An explicitly parenthesized arithmetic (and/or logical) expression clearly indicates the sequence of operations to be performed on its arguments. However, it is quite common that we do not write all the parentheses in such expressions. Instead, we use some rules of precedence and associativity, that make the sequence clear. For example, the expression

   a + b * c

conventionally stands for

   a + (b * c)

and not for

   (a + b) * c

The reason is that the multiplication operator has higher precedence than the addition operator. This means that * attracts the common operand b more forcibly than + does. As a result, b becomes an operand for * and not for +. Note that in general these two expressions evaluate to different values. For example, 40 + (15 * 7) equals 145, whereas (40 + 15) * 7 evaluates to 385. It is, therefore, necessary that when we write 40 + 15 * 7, we precisely understand which way we plan to resolve the ambiguity.

In order to explain another source of ambiguity, let us look at the expression

   a - b - c

Now the common operand b belongs to two same operators (subtraction). They have the same precedence. Now we can evaluate this as

   (a - b) - c

or as

   a - (b - c)

Again the two expressions may evaluate to different values. For example, (40 - 15) - 7 is 18, whereas 40 - (15 - 7) is 32. The convention is that the first interpretation is correct. In other words, the subtraction operator is left-associative.

C is no exception to these conventional interpretations. You need not fully parenthesize a composite expression. C applies the standard precedence rules for evaluating the expression. The following table describes the precedence and associativity rules for all the arithmetic and bitwise operators introduced so far. The table lists operators from higher to lower precedences, i.e., operators at later rows have lower precedences than operators at earlier rows.

Operator(s)TypeAssociativity
++  --
unarynon-associative
-  ~
unaryright
*  /  %
binaryleft
+  -
binaryleft
<<  >>
binaryleft
&
binaryleft
|  ^
binaryleft
=  +=  -=  *= etc.
binaryright


Course home