CSC 115.005/006 Sonoma State University Spring 2022
Scribbler 2
CSC 115.005/006:
Programming I
Scribbler 2
Instructor: Henry M. Walker

Lecturer, Sonoma State University
Professor Emeritus of Computer Science and Mathematics, Grinnell College


Course Home References Course Details: Syllabus, Schedule, Deadlines, Topic organization MyroC Documentation Project Scope/
Acknowledgments

Notes:

Reading on Arrays

Introduction

Consider the problem of repeatedly moving a robot and turning right. If the time for moving forward and the amount to turn right is the same throughout the activity, the relevant C code might be:

int i;
for (i = 0; i < 5; i++)
  {
     rForward (1.0, 0.5);  /* move forward at full speed for 1/2 second */
     rTurnRight (1.0, 0.5);/* turn right at full speed for 1/2 second */
   }

This works fine if the robot is to move the same amount forward and turn the same amount for each iteration. However, if the speed or time will change from one interval to the next, then we would need to store the specific values for each movement. Here is one approach:

  rForward (1.0, 0.5);
  rTurnRight (1.0, 0.5);
  
  rForward (0.75, 0.35);
  rTurnRight (0.6, 0.35);
  
  rForward (0.25, 0.4);
  rTurnRight (0.8, 0.4);
  
  rForward (1.0, 0.5);
  rTurnRight (0.4, 0.5);
  
  rForward (0.45, 0.65);
  rTurnRight (1.0, 0.65);

Although this approach works, the code can be somewhat hard to read, and the approach certainly does not generalize well. For example, in reading this code, it may not be apparent that the time devoted to moving forward is the same as the time devoted to turning.

Reading Outline


One way to make the code a little clearer would be to introduce variables for the various values:

  double forspeed0 = 1.0;
  double forspeed1 = 0.75;
  double forspeed2 = 0.25;
  double forspeed3 = 1.0;
  double forspeed4 = 0.45;

  double turnspeed0 = 1.0;
  double turnspeed1 = 0.6;
  double turnspeed2 = 0.8;
  double turnspeed3 = 0.4;
  double turnspeed4 = 1.0;

  double time0 = 0.5;
  double time1 = 0.35;
  double time2 = 0.4;
  double time3 = 0.5;
  double time4 = 0.65;

  rForward (forward0, time0);
  rTurnRight (turnspeed0, time0);
  
  rForward (forward1, time1);
  rTurnRight (turnspeed1, time1);
  
  rForward (forward2, time2);
  rTurnRight (turnspeed2, time2);
  
  rForward (forward3, time3);
  rTurnRight (turnspeed3, time3);
  
  rForward (forward4, time4);
  rTurnRight (turnspeed4, time4);

On the positive side, this code clarifies somewhat the meaning of the various numbers. On the negative side, this approach continues to be awkward for lengthy sequences of movements. Further, the variable names forspeed0, forspeed1, etc. are not very descriptive.

Some computer scientists quip that variables, such as b1, b2, etc. are fine if one is referring to vitamins, but these names do not convey much meaning in other contexts.


Arrays

Arrays allow similar types of data to be collected together and accessed using subscripts. When using an array, one variable name is used to reference the collection of values, and a subscript is used to indicate which item in the collection is to be used.


For example, the following declarations place the same numbers from above into three arrays:

  double forspeed[5]  = { 1.0, 0.75, 0.25, 1.0, 0.45 };
  double turnspeed[5] = { 1.0, 0.6, 0.8, 0.4, 1.0 }; 
  double time[5]      = { 0.5, 0.35, 0.4, 0.5, 0.65 };

In the declaration for forspeed, the clause [5] indicates that there will be 5 values stored, labeled forspeed[0], forspeed[1], forspeed[2], forspeed[3], and forspeed[4]. Next, the clause = { 1.0, 0.75, 0.25, 1.0, 0.45 } initializes the forspeed array. Similar comments apply to the turnspeed and time arrays.


With these declarations, the above robot motion translates to the following code segment:

  int i;
  for (i = 0; i < 5; i++)
     {
       rForward (forspeed[i], time[i]);
       rTurnRight (turnspeed[i], time[i]);
     }

In this code segment, i takes on successive values 0, 1, 2, 3, 4. Thus, the first time through the loop, i is 0, and the first command becomes

  rForward (forspeed[0], time[0]);

In this context, forspeed[0] is a double and can be used as any double variable.


Array Declarations

In declaring double forspeed[5], the compiler sets aside space for five double numbers. As this suggests, when an array is first declared, the compiler must know how much space to allocate. The size of any array is determined when it is first declared.

As an alternative to declaring and initializing an array at the same time, arrays may be declared first and then assigned values sometime later:

 double forspeed[5];

  ...

 forspeed[0] = 1.0;
 forspeed[1] = 0.75;
 forspeed[2] = 0.25;
 forspeed[3] = 1.0; 
 forspeed[4] = 0.45;

As still another alternative, we can declare and initialize an array and let the compiler count how large the resulting array should be:

  double forspeed[] = { 1.0, 0.75, 0.25, 1.0, 0.45 };
  double turnspeed[] = { 1.0, 0.6, 0.8, 0.4, 1.0 }; 
  double time[] = { 0.5, 0.35, 0.4, 0.5, 0.65 };

In this setting, we have specified 5 values for each array, so each array will be allocated space for 5 double values.

Warning: The declaration double forspeed[] may seem to allow the array to be flexible in size, but this is an illusion. The C compiler counts the number of items given and allocates that amount of space. In this case, the size of forspeed will be 5, whether or not we specify the array size.


As another variation, we are allowed to allocate a large array and initialize only the first part of it:

  double forspeed[8] = { 1.0, 0.75, 0.25, 1.0, 0.45 };

Here the code allocates space for 8 double numbers, forspeed[0], ... forspeed[7]. The first five of these are initialized. The values of the last three are not specified and C assigns them the value 0.0.

On the other hand, while C allows initialization of only part of an array, it does not allow declaration of an array that is too small for the initial values given:

  double forspeed[3] = { 1.0, 0.75, 0.25, 1.0, 0.45 }; 
      /* error:  5 values given, but only space for 3 doubles */

Storage of Array Values

As we will discover shortly, several properties of arrays within C relate directly to the way arrays are stored in main memory.

Within C, an array declaration allocates space for the number of array elements specified, and the variable name for the array indicates the starting location or address of the array within main memory.

To illustrate, consider the following array declaration:

int a[10] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3};  

Within main memory, this array is stored as a block of data, as follows:

array a in main memory

Given an array declaration int a[10],



A Sample Program

Program max-min.c illustrates many aspects of arrays discussed in this reading.

/* A program stores n numbers, and
   computes their maximum, minimum, and average.
 * This code illustrates built-in C-style arrays.
 */
#include <stdio.h>

#define n 10  /* number of elements to be processed in array */

int main (void)
{    int j;
     int max, min, sum;

     printf ("Program to process real numbers.\n");

The program defines an array of 10 integers, and computes their average, maximum, and minimum.


     /* declare array of n values  */
     int a[n] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3};  

The declaration of the array includes initialization. Since this array matches the one described earlier in this reading, the values are stored in main memory as illustrated in the above diagram.


     /* print the values in the array */
     printf ("the values stored in array a are:\n");
     for (j = 0; j < n; j++)
       {
         printf ("   index:  %2d,     array value:  %2d\n", j, a[j]);
       }

In accessing array elements,


     sum = max = min = a[0];    /* right to left assignment operator */

C allows several assignment statements to be nested. This concise statement is the same as the following longer version:

min = a[0];
max = min;
sum = max;

     for (j = 1; j < n; j++)
       { if (a[j] > max)
           max = a[j];     
         if (a[j] < min)
           min = a[j];
         sum += a[j];
       }

In computing a sum, maximum, or minimum, we start with the first element. We then examine each subsequent element in the array.


     printf ("Maximum:  %2d\n", max);
     printf ("Minimum:  %2d\n", min);
     printf ("Average:  %2d\n\n", sum/n);

In computing the average, this code uses integer division that drops any remainder. If a decimal result was desired, one could cast either sum or n or both to a double and change the printing format for the average to %5.2lf or equivalent.


     
     printf ("printing more array elements\n");
     for (j = -4; j < 14; j++)
       {
          printf ("a[%d] = %2d\n", j, a[j]);
       }

     return 0;
}

The output from one run of this program follows:

Program to process real numbers.
the values stored in array a are:
   index:   0,     array value:   3
   index:   1,     array value:   1
   index:   2,     array value:   4
   index:   3,     array value:   1
   index:   4,     array value:   5
   index:   5,     array value:   9
   index:   6,     array value:   2
   index:   7,     array value:   6
   index:   8,     array value:   5
   index:   9,     array value:   3
Maximum:   9
Minimum:   1
Average:   3

printing more array elements
a[-4] =  1
a[-3] =  9
a[-2] = -2
a[-1] =  0
a[0] =  3
a[1] =  1
a[2] =  4
a[3] =  1
a[4] =  5
a[5] =  9
a[6] =  2
a[7] =  6
a[8] =  5
a[9] =  3
a[10] = 2082268064
a[11] = 1124092083
a[12] = 1531808504
a[13] = 32767

In accessing array elements, C does not check the subscript bounds of an array. Rather it starts at the base address a and moves the number of elements specified by the index. Thus,



More Parameters, including Arrays

The concepts of passing arrays as parameters follows from the underlying philosophy of arrays in C. In order to understand array parameters, it helps to first review the mechanisms available for passing simple types (e.g., ints, doubles) to functions in C.

Summary of Value and Address Parameters

The lab on functions and parameters considered two basic types of function parameters:

Suppose variable number is declared as follows in a main function:

double number = 123.45;

Value Parameter Passage

In the following code, execution of the valueAsParameter function creates a new variable valueParm, and the call of the valueAsParameter copies a value to valueParm.

As an example, consider the following code:

void valueAsParameter (double valueParm)
{
   printf ("value of valueParm at start of valueAsParameter: %lf\n", valueParm);
   valueParm = 543.21;
   printf ("value of valueParm at end of valueAsParameter: %lf\n", valueParm);
}

int main ()
{
   double number = 123.45;
   valueAsParameter (number);
   printf ("value of number after valueAsParameter completed:  %lf\n", number);
}

When this code is executed,

Altogether, value parameter passage copies a value to the new parameter, work in the function works with the copied value, and changes to the new parameter do not affect the original variable (in main).

Passing Addresses as Parameters

In the following code, execution of the addressAsParameter function stores the address (not the value) of the original variable. Using the address as the parameter, changes at the stored address refer back to the main variable.

As an example, consider the following code:

void addressAsParameter (double *addrParm)
{
   printf ("value of valueParm at start of addressAsParameter: %lf\n", *addrParm);
   *addrParm = 543.21;
   printf ("value of valueParm at end of addressAsParameter: %lf\n", *addrParm);
}

int main ()
{
   double number = 123.45;
   addressAsParameter (&number);
   printf ("value of number after addressAsParameter completed:  %lf\n", number);
}

When this code is executed,

Array Parameters Work as Addresses

In C, the declaration

double numberArr [5] = {43.7, 23.1, -56.2, 98.6, -40.0};

allocates space for 5 double precision numbers and initializes those values. The variable numberArr refers to the address of the first array element. From a compiler's perspective, a reference to the variable numberArr usually is equivalent to the expression &numberArr[0]. (Documentation lists three exceptions, as noted in the given link.)

Since numberArr is actually an address, parameter passage for arrays involves the base address of the array — without specifying an ampersand &, the base address of the array is passed to the function.

As an example, consider the following code:

#include <stdio.h>

/* illustration of parameter passage of an array */

void arrFunc (double arrayParm[])
{
  int i;
  printf ("values of array at start of function: ");
  for (i = 0; i < 5; i++)
    printf ("%8.2lf", arrayParm[i]);
  printf ("\n");
  arrayParm[1] += 100;
  arrayParm[3] += 300;
  printf ("values of array at end of function:   ");
  for (i = 0; i < 5; i++)
    printf ("%8.2lf", arrayParm[i]);
  printf ("\n");
}

int main () 
{
  double numberArr [5] = {43.7, 23.1, -56.2, 98.6, -40.0};
  arrFunc(numberArr);
  int k;
  printf ("values of array at end of main:       ");
  for (k = 0; k < 5; k++)
    printf ("%8.2lf", numberArr[k]); 
  printf ("\n");

  return 0;
}

When this program is run, initial values are stored in the numberArr array. When function arrFunc is called, the base address of the numberArr array is copied to the arrayParm parameter, but the values within the array are not copied. Thus, array references within the arrFunc function refer to the original array — there is not another copy of the array.

The resulting output from this program follows:

values of array at start of function:    43.70   23.10  -56.20   98.60  -40.00
values of array at end of function:      43.70  123.10  -56.20  398.60  -40.00
values of array at end of main:          43.70  123.10  -56.20  398.60  -40.00

Note that the changes to array elements 2 and 4, made within the function, are recorded in the main array.

Observation

As a secondary observation, note that an array variable (e.g., numberArr) contains information about where the array begins. However, the array does NOT contain information about how long it is or where it stops. Thus, in the sample program, the programmer had to remember that numberArr was declared with 5 elements, and this information was hard coded into the program in both arrFunc and main.

If a function will be called with several arrays, it is common for an extra parameter (the array length) to be added, so the function will know how many array elements might be involved in processing.



This reading represents and merging of two previous readings, followed by a substantially expanded discussion.

Discussion of arrays and array declarations: Valid HTML 4.01! Valid CSS!
    created 30 October 2011 by Henry M. Walker
revised 22 September 2013 by Henry M. Walker
expanded and revised 8 August 2016 by Henry M. Walker
Discussion of array parameters
created 21 July 2012 by Henry M. Walker
revised 21 July 2012 by Henry M. Walker
Combined reading
Merged, expanded, and reformatted 8 August 2016 by Henry M. Walker
For more information, please contact Henry M. Walker at walker@cs.grinnell.edu.