Expressions

Expressions

In C, expressions are used to calculate some value. The simplest expressions are constants and variables. There is practically no limit to how complex an expression can be.

Examples:

i
5
3.1415
a + b
rate * time
x * (a + b / 7.0) - value / y
x*(a+b/7.0)-value/y
x   *(a+   b/     7.0)-    value    / y
sqrt(25.8) + b * abs(c)
Examples:
-i
+5
a + b
rate * time
Simple arithmetic unary operators:
Unary operator Meaning
+ Positive (redundant)
- Negation
Some simple arithmetic binary operators:
Binary operator Meaning
+ Add
- Subtract
* Multiply
/ Divide
% Modulo
(Remainder)
With the exception of the modulus operator, all other operators above can work with integral (whole numbers) and floating point types. The modulus operator can only be used with integral types.

Precedence and Associativity

Just as in mathematics, all operators have a certain precedence. Simply put, when more than one operator is used in an expression, precedence determines which one gets evaluated first.

3 + 4 * 2 is 11 and is the same as 3 + (4 * 2), although the parentheses are redundant

(3 + 4) * 2 is 14

-4 + 7 is 3 and is the same as (-4) + 7, although the parentheses are redundant

-(4 + 7) is -11
When two or more operators with the same precedence are used in an expression, you must look at the operator's associativity to determine the order of evaluation. The order is either left-to-right (L-R) or right-to-left (R-L).
3 + 4 + 2 is 9 and is the same as (3 + 4) + 2

3 * 4 * 2 is 24 and is the same as (3 * 4) * 2

2 * 6 / 4 is 3 and is the same as (2 * 6) / 4

2 * (6 / 4) is 2

This precedence chart shows that there are quite a few different levels of precedence within the C operators. Each division (separated by a horizontal line -----) is a different precedence level.

Assignment Operators

The assignment operator is very common. There are simple assignments and compound assignments.

Simple assignment statements:

a = 1;
b = 2;
a = b;
a = 3 * b;
a = 4 - 3 * b / 8;
Note that the = operator is assignment, not equality (which is ==, by the way).

Examples:

int i;    /* i holds an undefined value  */
double d; /* d holds an undefined value  */

i = 10;    /* i now holds the value 10   */
i = 12.8;  /* i now holds the value 12   */
d = 10;    /* d now holds the value 10.0 */
d = 12.8;  /* d now holds the value 12.8 */
The assignment operator is unique compared to the arithmetic operators we've seen so far: Example:
int a, b, c; /* all are undefined */

b = 5;       /* b is now 5  */
c = 10;      /* c is now 10 */

a = b + c;   /* a is now 15, b and c are unchanged */
a = b * c;   /* a is now 50, b and c are unchanged */
Because of the associativity of the assignment operator, we can do this:
a = b = c = 5; /* all are now 5 */
This is the same as this:
a = (b = (c = 5)); /* all are now 5 */
Note that this is very different (and is illegal):
((a = b) = c) = 5; /* This is not legal C code */
This is because the assignment operator requires an l-value, so we can store a value. These are illegal as well:
10 = 5;    /* Illegal */
10 = a;    /* Illegal */
a + b = 8; /* Illegal */
10 = 10;   /* Illegal */
Remember, this is assignment not equality. (The value on the left side is changing.)

Some languages use the ← symbol for assignment as in: a ← a + 5. This completely removes the ambiguity between assignment and equality. However, it doesn't work well with computers as there is no ← character on the keyboard. We'll see later that we can fake this symbol: → with these two characters: —> (minus and greater), which is used with pointers and structures.


Compound Assignment

Often, we'd like to add or subtract a value from a variable, and assign the new value back to the variable. This is completely legal (and sane):
  /* get the current value of a, add 5 to it, */
  /* and put the new value back into a        */
a = a + 5;  

  /* get the current value of b, subtract 6 from it, */
  /* and put the new value back into b               */
b = b - 6;  
These can be done more succinctly with compound assignment operators or arithmetic assignment operators:
  /* get the current value of a, add 5 to it, */
  /* and put the new value back into a        */
a += 5;  

  /* get the current value of b, subtract 6 from it, */
  /* and put the new value back into b               */
b -= 6;  
Note that += and -= are single tokens. You cannot insert a space. There is also a *= operator and a /= operator. (There are several more, which we'll see later.)


Increment and Decrement Operators

Adding one or subtracting one from a variable is a very common occurrence. Because of this, there are a few operators that are dedicated to this.

Pre-increment Post-increment Pre-decrement Post-decrement
++i i++ --i i--
These three assignment expressions are similar:

Assignment Compound Assignment Increment/Decrement
a = a + 1 a += 1  a++
++a
a = a - 1 a -= 1 a--
--a
There is an important but subtle difference between the prefix and postfix versions of the increment/decrement operators which causes the above to be not quite true. In other words, the value of these expressions are the same:
a = a + 1       a += 1      ++a
Notice the missing a++ expression. This means that if you displayed these expressions using printf, you'd see:
a = 5;
printf("value is %i\n", a = a + 1); /* value is 6 */

a = 5;
printf("value is %i\n", a += 1); /* value is 6 */

a = 5;
printf("value is %i\n", ++a); /* value is 6 */

a = 5;
printf("value is %i\n", a++); /* value is 5 */
However, as statements, these are all equivalent:
a = a + 1;
a += 1;
++a;
a++;
Here is a video that explains what's happening.

More examples: Assuming that a is an integer:

StatementsOutput
a = 5;
printf("The value of a is %i\n", ++a);
printf("The value of a is %i\n", a);
	
The value of a is 6
The value of a is 6
a = 5;
printf("The value of a is %i\n", a++);
printf("The value of a is %i\n", a);
	
The value of a is 5
The value of a is 6
a = 5;
printf("The value of a is %i\n", --a);
printf("The value of a is %i\n", a);
	
The value of a is 4
The value of a is 4
a = 5;
printf("The value of a is %i\n", a--);
printf("The value of a is %i\n", a);

	
The value of a is 5
The value of a is 4
Looking closer, this statement (foo1):
c = a++ + ++b;
is equivalent to these statements (foo2):
b = b + 1;
c = a + b;
a = a + 1;
Comparing the generated assembly code:
One statementThree statements
foo1:
  pushq %rbp  #
  movq  %rsp, %rbp  #,
  movl  %edi, -4(%rbp)  # a, a
  movl  %esi, -8(%rbp)  # b, b
  movl  %edx, -12(%rbp) # c, c

 # comp.c:3:   c = a++ + ++b;
  movl  -4(%rbp), %eax  # a, a.0_1
  leal  1(%rax), %edx #, tmp90
  movl  %edx, -4(%rbp)  # tmp90, a

# comp.c:3:   c = a++ + ++b;
  addl  $1, -8(%rbp)  #, b
  movl  -8(%rbp), %edx  # b, tmp94
  addl  %edx, %eax  # tmp94, tmp93
  movl  %eax, -12(%rbp) # tmp93, c


# comp.c:4:   return c;
  movl  -12(%rbp), %eax # c, _7

# comp.c:5: }
  popq  %rbp  #
  ret

foo2:
  pushq %rbp  #
  movq  %rsp, %rbp  #,
  movl  %edi, -4(%rbp)  # a, a
  movl  %esi, -8(%rbp)  # b, b
  movl  %edx, -12(%rbp) # c, c

# comp.c:9:   b = b + 1;
  addl  $1, -8(%rbp)  #, b

# comp.c:10:  c = a + b;
  movl  -4(%rbp), %edx  # a, tmp93
  movl  -8(%rbp), %eax  # b, tmp94
  addl  %edx, %eax  # tmp93, tmp92
  movl  %eax, -12(%rbp) # tmp92, c

# comp.c:11:  a = a + 1;
  addl  $1, -4(%rbp)  #, a

# comp.c:13:  return c;
  movl  -12(%rbp), %eax # c, _6

# comp.c:14: }
  popq  %rbp  #
  ret

Look closely at the expressions below to determine the output:

StatementsOutput
a = 5;
b = 3;
c = a++ + b++;
printf("a = %i, b = %i, c = %i\n", a, b, c);

a = 6, b = 4, c = 8

a = 5;
b = 3;
c = ++a + b++;
printf("a = %i, b = %i, c = %i\n", a, b, c);

a = 6, b = 4, c = 9

a = 5;
b = 3;
c = a++ + ++b;
printf("a = %i, b = %i, c = %i\n", a, b, c);

a = 6, b = 4, c = 9


a = 5;
b = 3;
c = ++a + ++b;
printf("a = %i, b = %i, c = %i\n", a, b, c);

a = 6, b = 4, c = 10

The statement below modifies the values of a, b, and c:
c = a++ + ++b;
Remember, to modify a variable means to change the value that is stored at the memory location represented by the variable. Graphically, a++ would look (simplified) something like this:
Fetch value from memoryIncrement the value by 1Store the new value in memory

Notice that there is a time when both the old value and the new value exist. This is key to understanding the increment/decrement operators.

Order of Evaluation

Example expressions:
int w = 1;
int x = 2;
int y = 3;
int z = 4;
int r;

r = w * x + y * z;     /* 1. same as: (w * x) + (y * z) */
r = w + x * y + z;     /* 2. same as: w + (x * y) + z   */
r = (w + x) * (y + z); /* 3. only way to write this     */
In the compound expressions above, there are actually several subexpressions in each. In this expression:
r = w * x + y * z     /* 1. same as: (w * x) + (y * z) */
  1. How many operators are there?
  2. Which arithmetic operation is performed last?
  3. Which arithmetic operation is performed first?
Here is a video that explains what's happening.

In other words, the assignment expression must perform each of these evaluations: (The registers used here are completely arbitrary, but you should get the idea.)

  1. The value stored at w must be fetched from memory. (Put in register A)
  2. The value stored at x must be fetched from memory. (Put in register B)
  3. The value stored at y must be fetched from memory. (Put in register C)
  4. The value stored at z must be fetched from memory. (Put in register D)
  5. The value in register A must be multiplied by the value in register B. (Put result in register E)
  6. The value in register C must be multiplied by the value in register D. (Put result in register F)
  7. The value in register E is added to the value in register F. (Put result in register G)
  8. The value in register G is stored in memory location r.
Interestingly, the order of some of these operations is undefined. For example, the first four instructions could actually be these:
  1. The value stored at z is fetched from memory. (Put in register A)
  2. The value stored at w is fetched from memory. (Put in register B)
  3. The value stored at x is fetched from memory. (Put in register C)
  4. The value stored at y is fetched from memory. (Put in register D)
The instructions could be ordered like this:
  1. The value stored at w is fetched from memory. (Put in register A)
  2. The value stored at x is fetched from memory. (Put in register B)
  3. The value in register A is multiplied by the value in register B. (Put result in register C)
  4. The value stored at y is fetched from memory. (Put in register A)
  5. The value stored at z is fetched from memory. (Put in register B)
  6. The value in register A is multiplied by the value in register B. (Put result in register D)
  7. The value in register C is added to the value in register D. (Put result in register E)
  8. The value in register E is stored in memory location r.
Or, the instructions could even be like this:
  1. The value stored at w is fetched from memory. (Put in register A)
  2. The value stored at x is fetched from memory. (Put in register B)
  3. The value in register A is multiplied by the value in register B. (Put result in register C)
  4. The value stored at y is fetched from memory. (Put in register A)
  5. The value stored at z is fetched from memory. (Put in register B)
  6. The value in register A is multiplied by the value in register B. (Put result in register A)
  7. The value in register A is added to the value in register C. (Put result in register B)
  8. The value in register B is stored in memory location r.
There are many other orderings that are equally valid. However, some subexpressions must be in a particular order. For example: So, even though the statement:
r = w * x + y * z;     /* 1. same as: (w * x) + (y * z) */
can be evaluated in a multitude of ways, the result will always be the same: 14

Notes:

Side-effects in Expressions

Anytime an operator causes a value in memory to change, it is called a side-effect operator. The most obvious side-effect operator is the assignment operator:
e = a * b + c * d;  /* Changes the value stored at e */
However, this statement is actually performing three assignments:
e = a++ * b++;  /* 3 modifications */
After the statement completely executes, e, a, and b will have different values.

This is problematic, though:

e = a++ * a;  /* dangerous code! */
Since a is used twice, it will be evaluated twice. And, depending on when the increment to a occurs, the result will be different:
a = 2;
e = a++ * a;  /* e is either 4 or 6 */
Here is a video that explains what's happening.

In fact, the GNU gcc compiler will actually warn you about it:

warning: operation on 'a' may be undefined
Here's a nasty example of undefined code:

#include <stdio.h>

int main(void)
{
  int a = 5;
  a = a-- - --a * (a = -3) * a++ + ++a;
  printf("a = %i\n", a);

  return 0;
}
Depending on which compiler you use, you may get different results:
CompilerOutputWarning
GNU gcc
a = 31
warning: operation on 'a' may be undefined [-Wsequence-point]
   a = a-- - --a * (a = -3) * a++ + ++a;
   ~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Clang
a = -23
warning: multiple unsequenced modifications to 'a' [-Wunsequenced]
  a = a-- - --a * (a = -3) * a++ + ++a;
       ^    ~~
Microsoft
a = 4
None.
Borland
a = 21
None.
Fortunately, the GNU compiler will warn you about this:
main.c: In function `main':
main.c:6: warning: operation on `a' may be undefined
main.c:6: warning: operation on `a' may be undefined
main.c:6: warning: operation on `a' may be undefined
main.c:6: warning: operation on `a' may be undefined
main.c:6: warning: operation on `a' may be undefined
Some side-effect operators:
=  +=  -=  *=  /=  
++ (pre/post increment)  
-- (pre/post decrement)
There are 4 side-effect operators in this completely valid expression: (How many tokens are in the expression?)
a = b += c++ - d + --e / -f	
What will be printed by this code? The first thing you should do is identify which variables are going to have their values changed. You'll definitely want to refer to the precedence chart.
int a = 1;
int b = 2;
int c = 3;
int d = 4;
int e = 5;
int f = 6;

a = b += c++ - d + --e / -f;

printf("a = %i, b = %i, c = %i, d = %i, e = %i, f = %i\n", a, b, c, d, e, f);
To better understand the expression above, you should add parentheses to explicitly show the order of evaluation.

Given this code:

int x = a * b + c * d + e * f;
Which value is fetched from memory first? (Compiler-dependent)

There are rules that dictate precedence and associativity, but there may still be ambiguity (due to side-effect operations).

More complex example (with visible side-effects):

int PrintAndReturn(int value)
{
  printf("%i\n", value);
  return value;
}

int main(void)
{
  int x, y;
  
  x = PrintAndReturn(1) + PrintAndReturn(2) + PrintAndReturn(3);
  printf("x = %i\n", x);
  
  y = PrintAndReturn(1) + PrintAndReturn(2) * PrintAndReturn(3);
  printf("y = %i\n", y);
  
  return 0;
}
Possible compiler evaluations:
Evaluation #1Evaluation #2Evaluation #3
t1 = PrintAndReturn(1);
t2 = PrintAndReturn(2);
t3 = PrintAndReturn(3);
t4 = t2 * t3;
 y = t1 + t4;
t1 = PrintAndReturn(3);
t2 = PrintAndReturn(2);
t3 = PrintAndReturn(1);
t4 = t1 * t2;
 y = t3 + t4;
t1 = PrintAndReturn(2);
t2 = PrintAndReturn(3);
t3 = PrintAndReturn(1);
t4 = t1 * t2;
 y = t3 + t4;
The order in which expressions are evalutated is more relevant when the expressions have side effects. All compilers will not generate the same output.
GNU               MS               Borland
1                 1                1
2                 2                2
3                 3                3
x = 6             x = 6            x = 6
1                 1                2
2                 2                3
3                 3                1
y = 7             y = 7            y = 7