C permits the expression of inherently efficient code

A common thread of thought permeats students who learn to program in C, after learning Java. They often wonder at C's expression of pointer (or address) arithmetic, and the way it is woven into the use of arrays. The reasoning behind and design of the C language facilitates the compiler's production of more efficient assembly language code. A C programmer who understands assembly language can sometimes write C source code that, even without common (current) compiler optimizations, simply is more efficient.

To see this in action, consider three different C code fragments that do exactly the same thing. Each uses a for loop to add the value 1 to each element of an array of (100) integers. With each of the C code fragments is a simplistic version of the assembly language code that would result from compilation.

Example 1

The C code that does not express array accesses using pointer arithmetic:

   int  a[100];

   for ( i = 0; i <= 99; i++ ) {
      a[i] = a[i] + 1;
   }

MIPS assembly language code for this C code:

   # $8 is i
   # $9 is the literal 99
   # $10 is the base address of a[]
   li   $9, 99
   la   $10, a
   li   $8, 0    # i = 0; for loop induction variable
for:
   bgt  $8, $9, end_for
   mult $11, $8, 4      # $11 is offset to correct array element
   add  $12, $8, $10    # $12 is address of a[i]
   lw   $13, ($12)          # load the array element
   add  $13, $13, 1         # add 1 to it
   sw   $13, ($12)          # store it back to memory
   add  $8, $8, 1       # i++; increment loop induction variable
   b    for
end_for:

This assembly language code fragment assumes that the compiler will move the instruction la $10, a outside the for loop. This is a compiler optimization.

Example 2

The C code:

   int  a[100];

   for ( i = 0; i <= 99; i++ ) {
      ( *(a + i) )++;
   }

The MIPS assembly language code for this C code is identical to the first example.

Example 3

The C code that uses only the pointer arithmetic:

   int  a[100];

   for ( ptr = a; ptr <= (a+99); ptr++ ) {
      (*ptr)++;
   }

The MIPS assembly language code:

   # $8 is ptr
   # $9 is the base address of a[]
   la   $9, a
   li   $10, 99
   mult $10, $10, 4
   add  $10, $10, $9      # $10 has address a+99; could be written &(a[99])
   move $8, $9            # ptr = a; set loop induction variable
for:
   bgt  $8, $10, end_for
   lw   $11, ($8)            # load array element
   add  $11, $11, 1          # add 1 to it
   sw   $11, ($8)            # store it back to memory
   add  $8, $8, 4         # ptr++; address arithmetic
   b    for
end_for:

Analysis of the 3 examples

When trying to consider efficiency, or speed of execution, we might use a count of the total number of instructions executed to complete the code fragment. A further assumption that each instruction takes the same amount of time to fetch and execute is close enough to the truth to be valid.

The assembly language code for examples 1 and 2 will fetch and execute

3 instructions before the loop
(8 * 100) + 1 instructions within the loop
= 804

The assembly language code for example 3 will fetch and execute

5 instructions before the loop
(6 * 100) + 1 instructions within the loop
= 606


Copyright © Karen Miller, 2011