Precedence, Sequence points, and Byte Ordering

"As soon as we started programming, we found to our surprise that it wasn't as easy to get programs right as we had thought. Debugging had to be discovered. I can remember the exact instant when I realized that a large part of my life from then on was going to be spent in finding mistakes in my own programmes." -- Maurice Wilkes discovers debugging, 1949.

L-value vs. R-value in a Nutshell (Review)

Examples of lvalues and rvalues:
int main(void)
{
    // ASSUME:
    // address of i is 100
    // address of j is 200
    // address of pi is 300
  int i, j; // 2 ints, i and j
  int *pi;  // pointer to an int

    // Ok
  i = 3;   // put 3 (literal) into location 100
  j = i;   // put 3 (contents of location 100) into location 200
  pi = &i; // put 100 (address of i) into location 300;
  j = *pi; // put 3 (contents of address 100) into location 200;
  *pi = 3; // put 3 (literal) into location 100 (not 300)

    // Errors
  pi = 3;  // Attempting to put 3 (literal) into location 300. 
           //   Error: 'int *' differs in levels of indirection from 'const int'
           
  3 = i;   // Error: left operand must be l-value
  
  return 0;
}
Simple guidelines: We will see a lot more about r-values and l-values in this course.


Expression Evaluation

Expressions are evaluated based on: There are rules that dictate precedence and associativity, but there is still ambiguity: Examples:
int x = a * b + c * d + e * f;
More complex example:
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;
}
The order in which expressions are evalutated is more relevant when the expressions have side effects.


Sequence Guarantee Points

Most expressions in C/C++ consist of subexpressions, meaning that the expressions are made up of other expressions:
a = (b + c) * d / (e - 4) * (f + 5 * g);
The order that these subexpressions are evaluated can change the value of the expression. For example, what is the value of c in the code below?
int a[4] = {1, 2, 3, 4};
int b[4] = {5, 6, 7, 8};
int i = 0;

int c = a[i++] + b[i]; /* i++ causes a side-effect */
The answer is: it depends. It depends on when the variable i was incremented. The value of c is going to be either 6 or 7. Do you see why?
a[i] + b[i]

  or

a[i] + b[i + 1]
GNU's gcc compiler gives a warning to the above:
warning: operation on `i' may be undefined

Sequence Guarantee Operators

There are 4 sequence guarantee operators:

     Operator          Symbol      Syntax
------------------------------------------------------
logical AND operator     &&     expr1 && expr2
logical OR operator      ||     expr1 || expr2 
conditional operator     ? :    expr1 ? expr2 : expr3 
comma operator           ,      expr1, expr2
Also, the semi-colon is a sequence guarantee point and means that any side-effect operator is guaranteed to have "done its duty" when you reach the semi-colon.

There are several side-effect operators:

     Operator                  Meaning
-------------------------------------------------------
=                          Assignment
++  --                     Pre/Post increment/decrement
+=, -=, *=, /=, %=         Arithmetic assignments
&=, !=, ^=, <<=, >>=       Bitwise assignments
Examples using the comma operator

What do the following functions print?

void TestCommaSequence1(void)
{
  int a = (1, 2, 3, 4);  

  printf("a = %i\n", a);
}
With side-effect operators:
void TestCommaSequence2(void)
{
  int a, b;
  
  a = printf("1\n"), printf("22\n"), printf("333\n"), printf("4444\n");
  printf("a = %i\n", a);

  b = (printf("1\n"), printf("22\n"), printf("333\n"), printf("4444\n"));
  printf("b = %i\n", b);
}

When using side-effect operators (and you will use them all the time), you need to make sure that the meaning of the entire expression is not ambiguous. If it is ambiguous (ill-formed or mal-formed), it is not guaranteed to evaluate the same in all situations (read: compilers).

GNU's gcc compiler warns about the function above:

warning: left-hand operand of comma expression has no effect
Additional Resources:

Sequence Points and Expression Evaluation
Sequence Points Overcomplex statements can confuse your compiler. Knowing where the sequence points are can help make your intentions clear
Microsoft's implementation of its sequence guarantee points.


Endian

More technically: Some processors and their "endianess":
Processor Family           Endian
---------------------------------
Pentium (Intel)            Little
Athlon (AMD)               Little
Alpha (DEC/Compaq)         Little
680x0 (Motorola)            Big
PowerPC (Motorola & IBM)    Big
SPARC (Sun)                 Big
MIPS (SGI)                  Big
Java Virtual Machine        Big
Network issues: Macros to swap 16-bit and 32-bit values:

#ifdef BIG_ENDIAN /* no-ops */

  #define htons(x) (x)  /* host-to-network short (16-bit) */
  #define htonl(x) (x)  /* host-to-network long (32-bit)  */
  #define ntohs(x) (x)  /* network-to-host short (16-bit) */
  #define ntohl(x) (x)  /* network-to-host long (32-bit)  */

#else /* assume little endian and swap bytes */

  #define htons(x) ((((x) & 0xff00) >> 8) | ((x) & 0x00ff) << 8)) 
  #define htonl(x) ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \
                   (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24))
  #define ntohs htons
  #define ntohl htonl

#endif

TIFF picture with byte-order encoded in first 16-bit word. You'll need to open the file in a hex editor to see the bytes.

Introduction to Endian "One, little two, little three little Endians..." This page gives a brief overview of big vs. little endian.

Some file formats and their endianness

Exercise: Write a function that determines whether your computer is little-endian or big-endian.