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:

Given an array declaration int a[10],
-
The variable name a indicates the base address of the array — the location in main memory where the block of memory for the array starts.
-
All access to elements in the array follows a brief computation: when accessing a[j], the computer starts at the base address a and then moves j elements to find the value under consideration.
- To access a[0], start at the base address for a, and do not move anywhere.
- To access a[1], start at the base address for a, and move ahead one data element (e.g., move the size of one int).
- To access a[2], start at the base address for a, and move ahead two data elements (e.g., move the size of two ints).
- Etc.
-
Since the index j for a[j] specifies the distance to move from the base address, arrays are said to be 0 indexed; the first element of the array is a[0], because the element does not require a move from the base address.
-
The access of any element in an array is reasonably quick and does not depend upon the size of the array. One always starts at the base address and moves over the specified number of elements.
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,
-
the index (j in this program) is placed in square brackets, after the array name (a in the program). Thus, a[0] identifies the first element in the array.
-
The use of the variable j here is of little consequence, except that j is in a loop and takes on the successive values 0, 1, 2, ... .
-
More generally, any variable name could be used as an index, provided the variable had appropriate values, such as 0, 1, 2, etc.
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.
-
If the array element a[j] is larger than the previously identified maximum, we update the recorded maximum.
-
If the array element a[j] is smaller than the previously identified minimum, we update the recorded minimum.
-
Whatever the array element's value, we add it to the running sum.
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,
-
the first element printed will be a[-4]: four elements before the start of the array. This element may be a valid memory location, but it is not part of the array. Rather, the value printed will be whatever might be found there — perhaps the value of another variable, or perhaps junk!
-
For large subscripts, the computer looks at main memory following the end of the array. If this memory location is valid, the machine again prints whatever it finds. Since C may not initialize main memory before it is used, the values printed may seem large and possibly random — whatever might have been left over from previous processing!
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,
- number gets an initial value of 123.45
- when valueAsParameter is called,
- new storage is allocated for valueParm
- 123.45 is copied into valueParm
- the value of valueParm (123.45) is printed
- the value stored in valueParm is changed to 543.21 (but the value stored in number is unaffected)
- the new value 543.21 of valueParm is printed
- when valueAsParameter is done, valueParm is deallocated, and the value 543.21 is lost
- number remains 123.45, and this number is printed in main
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,
- number gets an initial value of 123.45
- when addressAsParameter is called,
- addrParm is given the address of variable number
- the value in the location reference by addrParm (123.45) is printed
- the value at the location reference by addrParm is changed to 543.21 (i.e., the value stored in number is changed)
- the new value 543.21 referenced by addrParm is printed
- when addressAsParameter is done, addrParm is deallocated, but the changed value in number remains
- number contains 543.21, and this number is printed in main
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: |
![]() ![]() |
||
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. |