Skip to content
Physics and Astronomy
Home Our Teaching Resources C programming Arrays, etc.
Back to top
On this page
Contents

Arrays, etc.

Arrays or "numbered variables"

Warning: do not scroll past the heading "What is an array?" until you have answered the warm-up questions.

Vectors and matrices

As scientists we often deal with vectors of length N with coefficients V1, V2, Vk, etc.

Clearly it would be a nightmare to have to declare N separate variable with names like V1, V2, V7. Instead we need to be able to declare a "super-variable" to represent the whole vector and to be able to use expressions such as Vk to access the element we want. C's arrays allow us to represent vectors and matrices directly.

Locations as mathematical expressions

If we want to have a single "super-variable" to represent a whole vector of doubles then each value of Vk will need four or eight bytes to store it. It follows that the address of Vk must be a mathematical expression that depnds on k. That is, we are moving from machine code that looks like double@constant to machine code that looks like double@(expression) and that expression must depend upon k.

Wider use

Although we tend to think of vectors and matrices in their mathematical sense with operations such as division, multiplication etc. there is also a more general non-mathematical use of "Thing number 1", "Thing number 2", which use the same underlying mechanism. Indeed modern programs can easily use hundreds of mega-bytes or even gigabytes of memory and only a tiny fraction of that is for named variables with addresses that are constants that are known at compile time. Most data is accessed via machine-code that looks like typename@(expression), just like the vectors described above.

This means that discusing how vectors, and to a lesser extent matrices, are implmented and passed to functions represents a good introduction to a concept that is extremely important in C programing and which will form the basis of much of the next three lessons.

Preparation

Warm-up discussion 1: Storing vectors

Suppose we wish to store the elements of a vector as doubles the computer's memory and it turns out the the value of the first element of the vector is stored at location 400.

  1. If the first element of the vector is stored at location 400 where would be the simplest and most logical place to store the second element? (Remember that doubles require eight bytes each)
  2. Where would be the simplest and most logical place to store the third element?
  3. Provide a simple mathematical expression for the simplest and most logical place to store the Kth element.

Warm-up discussion 2: numbering things

Since arrays are all about numbered variables, let's briefly think about how we number things.

  • What was the first year of the twenty-first century?
  • If this is the twenty-first century, why do the years start with "20"?
  • Is "12 pm" twelve noon or twelve midnight? )
  • What's your reason for the previous answer?

Although intuitively our reaction is usually to number things starting from "1", as these warm-up discussion show, this tends to lead to problems later on. Therefore all modern numbering systems tend to start from zero. For example, the twenty-four clock numbers its hours from 0-23 (not 1-24) and its minutes and seconds from 0-59.

We shall return to this a little later in the lesson.

What is an array?

Hopefully in the above discussion you decided that if the first element of an array of doubles is stored at address 400 then the logical place to store the second element was at location 408!

An array is a series of objects of the same type stored sequentially in the computer's memory.

We can make arrays of anything we like, such as arrays of ints, arrays of doubles, arrays of double complexes etc. As we shall see below we can even make arrays of arrays! (Otherwise known as two-dimensional arrays.)

All arrays have both a type and a length.

Arrays are used where we have a set of things of the same type and we need to access each element by number, not by name.

When to use arrays

Coordinates, vectors (and later, matrices)

When referring to the position of a point in space we use a vector of three coordinates, e.g. (1.0, 1.0, 2.3). More generally, we refer to the components on an n-dimensional vector, V, as Vi where i is an integer, and a matrix, M, is a two-dimensional array of numbers. Mi j.

It is usually better to use an array for coordinates than individual variables x, y and z.

Arrays give us a convenient way of defining these and referring to their individual components, or elements, as v[i], m[i][j], etc.

If set up an array called, say myarray of length twenty, we can then treat any of its elements, such as "myarray[4]", "myarray[17]" just like a variable. We can also use expressions such as "myarray[i]", etc., which is why arrays are so useful.

Arrays can allow us to think at a higher level, in terms of vectors and matrices rather than their individual components.

Sequences of values

If we are looking at the population of a country over time we may have an array of integers, one for each year. Or we may be measuring or calculating a quantity that changes with time and we will have an array of doubles to store the values at different points in time. In each of these examples if we want to do something for every member of the sequence we can do it with a loop over the indices rather than just repeating the same code many times over.

Arrays help us to avoid "copy-paste-and-edit loops" in much the same way as functions do.

Declaring and using a one-dimensional array

The obvious, although not the only, way to create an array is simply to declare it in the same way we would declare an ordinary variable. C uses square brackets [] to designate the number in an array, both when declaring it and when accessing the individual elements.

Example: declaration and simple assignment

The following code sets up an array of three doubles and then sets the value of its elements.

#include <stdio.h>
int main() {
  double sides[3];  // Declare the array "sides"

  sides[0] = 5.2;
  sides[1] = 3.1;
  sides[2] = 4.6;

  // ... Do something interesting 
  return 0;
}
Step through this code


The first statement ("double sides[3];") reserves enough space to store three values, in this case doubles. Arrays can have any type: int, long, etc. We have given the array the name sides, which suggests we are dealing with a triangle. The following lines set the values of the three individual elements. The number inside the [] is referred to as that element's index and can be any integer expression, not just a constant.

  1. int myarray[N] (note the typename "int") declares a brand new array of N ints.
  2. myarray[j] on its own (note no "int") refers to one particular element of the array.
  1. Step through the above code
  2. To show how arrays are used and how they are stored in memory.
  3. Step through the above code, seeing how:
    • The array is declared.
    • The individual elements are accessed.
  4. Reload the page and select either "Combined view" or "Computer view". The main difference from what we have seen in previous codes is that the addresses are expressions rather than constants.
  5. Check that way the compiler is storing the elements of the array is the same as your answer in the preparation section (bearing in mind that the first array element is numbered zero.)
  6. Step through the code seeing how the address maths picks out each element in turn.

The first element of the array has index zero, the last has index N-1

As can be seen above, C numbers its arrays from zero. Although intuitively our reaction is usually to number things starting from "1", as the warm-up discussion shows, it tends to lead to problems later on. Therefore all modern numbering systems tend to start from zero. For example, the twenty-four clock numbers its hours from 0-23 (not 1-24) and its minutes and seconds from 0-59.

If we have an array of size N and the first element has index zero then the last element must have index N-1, not N!.

The classic array error

Be sure to remember this one!

  double sides[3];

  sides[3] = 1.7;  /* WRONG ! */

This is a particularly nasty bug which C does not check for. Trying to access the N+1th value of an array of size N is like standing three steps away from a cliff-top and taking four steps forward.

The elements of an array of size N are array[0] to array[N-1] inclusive.

Using scanf()

We can use scanf() to read in sides[0] and sides[1] just like a variable (see the complete example):

  scanf("%lg %lg", &sides[0], &sides[1]);
 


Here &sides[0] does what we want it to: it's the address of sides[0] .

Array initialisation

Arrays can have their values initialised at the point of declaration to make it more difficult to forget to miss one out. This is done using { ... } :

  double sides[3] = { 5.2, 3.1, 4.6 };

We have already seen { ... } used to group several statements together, here they are used to group several initialisers together.

Arrays can be inialised on declaration by putting the values inside { }
int myarray[3] = { 1, 2, 3 };

Two convenient array initialisation short cuts

Automatic array sizing

If we miss out the array size the compiler infers the array size from the number of initialisers:

  double sides[] = {  5.2, 3.1, 4.6 };

The compiler can infer the array size from the number of initialisers.

Missing initialisers are treated as zeros

Conversely, if we provide fewer initialisers than required the rest of the array elements are initialised to zero:

  int myarray[3] = { 1 };

Here we have shown an array of integers for variety. myarray[0] is initialised to 1, the other two elements to zero (not one!!). This does not apply to uninitialised arrays, only to arrays with fewer initialisers than values. To initialise an array to all zero use:

  int anotherarray[3] = { 0 };

Missing initialisers are treated as zeros but this does not apply to uninitialised arrays.

Looping over an array

The for() loop:
for(Initialise; Test; Increment)

  1. Initialise
  2. Loop:
    1. Test and if false quit the loop.
    2. Execute the body.
    3. Increment.

In the example above the array indices were constants (0, 1 and 2). Whilst this is of course legal, the whole point of using arrays is that we can use indices that are expressions. A common example is looping over all the elements of an array in turn. We can use the " for()" loop to loop over an entire array, in this case to read it in:

#include <stdio.h>
int main() {
  int numbers[10], k;

  for (int i = 0;  i < 10; ++i ) { 
    printf("Please enter value %d\n", i);
    scanf("%d", &numbers[i]);
  }

  for (int i = 0;  i < 10; ++i )
    printf("Value %d: %d\n", i, numbers[i]);

  return 0;
}
Step through this code

 

The for(Initialise; Test; Increment) loop can be used to loop over the elements of an array.

Avoid "magic numbers", use symbolic constants instead.

Remember: we don't give ourselves the opportunity to go wrong!

In this code the size of the array (10) is hard-coded in two different places. This is sometimes referred to as a "magic number": it's just there and we have no idea why. If we were to change the size of the array to 12 we would have to change the limits of our loops to 12 as well. Worse, there may be other occurrences of the number 10 that we don't have to change, as they have the value 10 for a different reason. And if we see the number "20" we will need to ask if it should now have the value "24".

The following # (preprocessor) line allows us to give a name to a constant:

#define VALUES 10
 
  • Any subsequent occurrence of the word VALUES (outside of text strings) is replaced by the rest of the line; in this case "10". Note there is no semi-colon!
  • Like all "#" directives, it is line-based with no semi-colon
  • It's conventional to use ALL UPPER-CASE letters, and digits for symbolic constants.

We shall expect you to always use symbolic constants for array dimensions, except for applications where it is logically impossible to change the array size, for example when dealing with a triangle.

NB: #define can be used for non-numerical constants as well.

Use #define to avoid "magic constants".

Example

#include <stdio.h>
#define NSTARS 10
void loopdemo2(void) {
  double mass[NSTARS];

  for (int star = 0; star < NSTARS; ++star ) {
    printf("Please enter mass of star %d\n", star);
    scanf("%lg", &mass[star]);
  }
  // ... Do something interesting 
}
Step through this code

 

Run-time array sizes

The original version of the C language required the dimensions of an array to be constants, defined when the program was written but this restriction has now been relaxed. It is occasionally useful to be able to write:

#include <stdio.h>
void myfun(int n) {
  int x[n];

  for(int i = 0; i < n; ++i)
    scanf("%d", &x[i]);
  ...
}

int main() {
  myfun(2);
  myfun(6);
  ...
  return 0;
}
Step through this code

 

How arrays work

Arrays serve as a useful introduction to accessing data via its memory address rather than by name. This is (in)famously the most confusing part of C for beginners which is why we are taking the chance to look at this in a little more detail now.

We have already said  that an array is a sequence of objects stored one-after the other in the computers memory. For example, if we have an array of four-byte integers called iarray, let us suppose the compiler has chosen to store its first element, iarray[0] at byte 640. Then iarray[1] will be stored at byte 644, iarray[2] will be stored at byte 648, etc:

        -------------------------------------------------------------------------
Byte:   | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | etc.
        -------------------------------------------------------------------------
Stores: | <---- iarray[0] ----> | <---- iarray[1] ----> | <---- iarray[2] ----> | etc.
        -------------------------------------------------------------------------

This method arrayname[index] is specifically designed to provide a natural and easy method for referring to array elements.

Hopefully our answers to the initial questions in this lesson should have lead us to the conclusion that the "computer's view" of the expression for element j of an array of doubles starting at location 400 is float@(400+8*j), or in the above example the computer's view of iarray[j] is int@(640+4*j).

C being C, it provides two simple rules for first finding the start of the array, and second going along it and finding what is there.

Rule 1: In an expression C treats the name of an array as meaning the address of its first element

Expressions have both a value and a type.

When (the of the name of) an array of Things is used in an expression it is replaced by the address of its first element, and its type is the address of a Thing (or "pointer to a Thing") and is a constant.The type is used both for traversing the array and for working out what to do when it gets there.

So in the above example iarray is a constant and in the example above, has the value "640" and type "address of an int". We can even print out the value of iarray using the "%p" format used for addresses.

The name of an array is a shorthand for the address of its first element (in both value and type).

	  myfunction(arrayname);
	

	  myfunction(&arrayname[0]);
	

Rule 2: If a is the address of a Thing then a[k] means "the Thing whose address is (a + k * the number of bytes needed to store a Thing)"


If a is the address of a Thing then a[k] means "the Thing whose address is (a + k * the size of a Thing)".

This combination of these two rules is what makes the "ar[n]" syntax work: if ar is an array of ints then the construct "ar[n]" is just a synonym for "go to location ar and move along by n times the size of an int and see what's there".

One result of this is that unlike some more specialised languages, C has no way of referring to an entire array (only its type and the address of its first element) and does not allow whole-array operations:

  int myarray[N], yourarray[N];
  ...
  yourarray = myarray;         // Wrong!

  for (i = 0; i < N; ++i)      // Correct
    yourarray[i] = myarray[i];

Note: this rule is both more simple and more subtle than it may first appear. For a variable x then the value of x is found by going to the address of x (in the computer's memory) and seeing what is stored there. For an array ar the "value" of ar is just the address of ar[0] which is a constant and the content of the computer's memory is not looked at.


The array type is used:
  1. To know how many bytes to move along when traversing the array.
  2. To know how to interpret the bytes stored there.

Notes

  • No array bound checking. The above notes have the disconcerting property that the compiler can follow the rules without needing to know the length of the array! In fact for a one-dimensional array the declaration is the only place the array size is used. The instructions generated by "ar[k]" are entirely independent of the length of the array and it is our responsibility not to go over the edge.
  • Address offsets are always multiplied by the size of the object it is an array of. If C did not do this then for an ar[6] would be size bytes from ar[0], so for an array of doubles this would land us into the middle of the first element, which would be a disaster.
  • The address[index] notation is specifically designed for use with arrays as it provides a simple way to say "go to this address, move along by this much and see what's there" (eg double@(address + 8*i)). We shall see in the next lesson an simpler notation for the situation where we just want to say "go to this address and see what's there", (eg double@address rather than double@(address + 0*i).

After an array has been declared the only thing the compiler knows is the address and type of its first element.

There is no array bound checking, we are responsible for this ourselves.

Stepping over the edge of the cliff

If in our previous example we assume that ar had dimension 10, then the compiler would have reserved forty bytes (640 to 679 inclusive) to store it. This prompts the question: "Exactly what happens if I try to assign a value to the eleventh element, ar[10]?" The answer is that the computer will try to write four bytes to location 680. In other words, it doesn't do a check for us.

This will most likely result in one of two things:

  • The variable whose address is 680 will have its value mysteriously changed. This tends to lead to random and confusing behaviour.
  • This will be a piece of memory we are not allowed to access and the program will die with a "segmentation fault" or SIGSEGV as it is also called.

Exactly the same will happen if we pass the wrong address to scanf(), or we try to access a file we have opened with fopen() that we have not checked for being NULL.

If we go a bit over the end of an array we will change the values of random variables, if we go a lot over the end we are likely to crash the entire program.

SIGSEGV or randomly-changing variables are likely to be due to going over the end of an array, bad arguments to scanf() or failing to check for failed calls to fopen().

Passing the address of an array to a function

One of the main reasons for the rule "The name of the array is equal to the address of the start of the array" is that having declared an array in one function, we often want to "pass that array to another function".

We mentioned above that the name of an array (say x) is just a synonym for the address of its first element. This has the some-what counterintuitive effect that any time we see the name of the array x on its own it's exactly the same as writing &x[0]:

	  double x[N];
	    
	  myfunction(x);
	

is exactly equivalent to:

	  double x[N];
	  myfunction(&x[0]);
	

The fact that passing the name of the array x to a function does nothing more than pass &x is rather surprising, and to be honest disappointing. We might have hoped that passing x would pass some high-level information concerning not only where x was being stored but also its dimensions etc.

Example: normalising a vector

A common requirement in science and maths is to normalise a vector, that is scale it so that its magnitude is one. The following code normalises an array of N doubles:

  // Normalise the vector a:
  double norm = 0;
  for(int i = 0; i < N; ++i)
    norm += a[i]*a[i];

  if ( norm > 0) {
    norm = sqrt(norm);
    for(int i = 0; i < N; ++i)
      a[i] /= norm;
  }
 

(Note: the statement a[i] /= norm; means "divide a[i] by norm".)

But suppose we have three vectors to normalise, a, b and c? We could Copy-and-Paste the code and then replace "a" by "b", and then again for "c". But this would lose us a lot of marks(!). What we would really like to do is to be able to write a function to normalise a vector and then write something like:

  normalise(a);
  normalise(b);
  normalise(c);
 

This is far from a theoretical example: vectors and matrices are very important in science and there is a wealth of libraries to do high-level operations such as inverting matrices, solving systems of linear equations, etc. A language that did not let us write functions to manipulate vectors and matrices would be a serious problem for scientists. Notice also that normalise() needs to work on the original arrays a, b and c. It would be no use passing it a copy of the array and normalising the local copy.

In order to work on the original array the function normalise() will need to know where it is stored in memory. Fortunately rule 1 says:

The name of an array is a shorthand for the address of its first element (in both value and type).

That is to say that in the three calls to normalise() above  we are passing the function the address of the first element of the array, just like we pass an address to scanf(). And of course, if normalise() knows the type and location of the first element it knows the location of the second, the third and so on. (But it doesn't know where it ends unless we tell it!) 

Kernighan and Ritchie wrote the book ("K&R") that first defined the C language.

When an array name is passed to a function, what is passed is the location of the beginning of the array.

[Kernighan and Ritchie: The C Programming Language]

Writing the normalise() function

The body of the function will look exactly the same as above, although for clarity we shall rename "a" to "p". It will look a bit like this, although we are not yet sure what the parameters will look like:

// Normalise a vector (an array) of length N
void normalise(What goes here?) {
  double norm = 0;
  for(int i = 0; i < N; ++i)
    norm += p[i]*p[i];

  if ( norm > 0) {
    norm = sqrt(norm);
    for(int i = 0; i < N; ++i)
    p[i] /= norm;
  }
}
 

Looking first at the body of the function we see the familiar notation p[i] to access an array element. The value of p must be the address of a[0] when the function is called with a as an argument, the address of b[0] when called with b as an argument and the address of c[0] when called with c as an argument. So p must be a variable, specifically p must a variable whose value is the address of the first element of the array we wish to normalise.If the C programming language did not provide us with this ability then we would be unable to write functions that acted on vectors and matrices declared inside other functions.

For example, let's assume that the array a is stored starting at location 200, then the comparison between a[i] inside main() and p[i] inside normalise() is as follows:

Expression
Type
Expands to
Use
a[i] a is an array (constant) double@(200+8*i) Can only process the array a
p[i] p is a variable double@(p+8*i) Can process any array provided we set the value of p to the address of its first element

If the first time we set p to be the address of a[0], the second to the address of b[0] and the third to the address of c[0] then the one function can be used to normalise all three arrays.

We saw above that the function call normalise(a) passes the address of a[0] to the function. Since this is precisely the value p needs to have it's clear that p is the parameter to normalise(). The function prototype must be something like:
void normalise(something p);

The parameter type must say first that p is a memory address but also that it is the memory address of a double and not, for example, a float so that when the compiler encounters the code p[i] it knows to generate double@(p+8*i) and not float@(p+4*i).

Fortunately we don't need to worry too much about how to declare p due to the following useful trick:

C makes it simple for us

C gives us a simple rule:

As a convenience to the programmer, when a function expects to be passed the address of the first element of an array the corresponding parameter can just be declared with the same declaration as the passed array with the left-hand dimension being ignored.

Not only is the left-hand dimension ignored, it can be omitted altogether which is a good idea that it emphasises we have not created a new array. For a one-dimensional array there is only one dimension of course so in our case the function prototype is just:


void normalise(double p[]);
 

This does not mean that p is a duplicate of the original array, it means it is a variable ready to receive the address of an array, storing a memory address with the property that when the compiler sees p[k] it accesses exactly the same memory address as it would had output been an array of that type. Of course, it's our job to ensure the function is passed a legitimate memory address.

This has two consequences:

  1. The computer only has to pass a single address (typically four or eight bytes) to the function not the entire array, in this case a million elements.
  2. When a function modifies the array it is modifying the original array.

Example: the full program.

The full program is:

#include <stdio.h>
#include <math.h>
#define N 3

void normalise(double p[]);

int main() {
  double a[N] = {2.7, 5.3, 8.3};
  double b[N] = {5.8, 2.9, 3.0};
  double c[N] = {-9.6, 13.9, 6.41};

  normalise(a);
  normalise(b);
  normalise(c);
  
  // More stuff here..
  
  return 0;
}

// Normalise a vector (an array) of length N
void normalise(double p[]) {
  double norm = 0;
  for(int i = 0; i < N; ++i)
    norm += p[i]*p[i];

  if ( norm > 0) {
    norm = sqrt(norm);
    for(int i = 0; i < N; ++i)
    p[i] /= norm;
  }
}
Step through this code

 

Example: using functions to set up and print an array

Just to give another example, here we declare an array inside of main() and pass it to two functions one on which gives the array some values, the other prints it to the screen.

//
// Demonstrate passing an array to functions
//
#include <stdio.h>
#define VALUES 4
// Note: the two forms of the prototype: one with the
// dimension, which is ignore, and one without. 
void setuparray(int input[VALUES]);
void printarray(int output[]);

int main() {
  int num[VALUES];

  setuparray(num);
  printarray(num);

  return 0;
}

void setuparray(int input[VALUES]) {
  int i;
  // Initialise to uninteresting values
  for (i = 0; i < VALUES; ++i)
    input[i] = i;
}

void printarray(int output[]) {
  int i;

  for (i = 0; i < VALUES; ++i ) {
    printf("%d\n", output[i]);
  }
}
Step through this code

 

Realistically this gives us a slight problem: printarray() has a loop and we need to know when it finishes so in practice we are likely to write:

void printarray(int output[], int n);

here n may be less than or equal to the true size of the array but should not be larger!

Being able to have a variable whose value is the address of another variable (a pointer) offers us tremendous flexibility:

  • We can write a function that can operate on an array declared inside another function.
  • We can not only read the values of array elemnts we can change them.

These advantages are useful in several other areas and so we will investigate pointers a little more over the next few lessons.

Arrays of arrays, or multi-dimensional arrays

Warm-up discussion 2: Storing matrices

Suppose we wish to store the elements of a two-dimensional 4x10 square matrix of floats, that is four rows of ten values, and that the first row starts at location 1000.

  1. If the first element of the first row of 10 floats is stored at location 1000 where would be the simplest and most logical place to store the first element of the second row? (Remember that floats require four bytes each)
  2. Provide a simple mathematical expression for the simplest and most logical place to store the start Jth row.
  3. Provide a simple mathematical expression for the simplest and most logical place to store the Kth element of the Jth row.

Multi-dimensional arrays are simple and straightforward and fit in well with our commonly-used ideas of matrices and "more than one vector"

Example: simultaneous equations

When solving simultaneous equations it's common to have a set of vectors Ej with Ejk referring to the kth component of jth vector. Furthermore it's also then common to think of E as a matrix in its own right ("the matrix formed by the vectors Ej"). So for a 3x3 set of equations we might write:

E00 X0 +  E01 X1 + E02 X2 = C0

E10 X0 +  E11 X1 + E12 X2 = C1

E20 X0 +  E21 X1 + E22 X2 = C2

C's ability to make an "array of anythings" makes this very natural and easy. We just define an "array of arrays" and  write:

  double vectors[NVECS][VECLEN];
  ...
  for(int v = 0; v < NVECS; ++v)
    printarray(vectors[v]);

 Almost anything we can have one of, we can make an array of.

Matrices and collections of several vectors are one and the same thing

It follows that if vectors[v] is the vth vector then vectors[v][k] is the kth element of vth vector. In other words an array of (one-dimensional) arrays of doubles, can equally be thought of as a two-dimensional array of doubles. The following example declares and initialises a two dimensional array.

In some situations it may be more natural to use NROWS, NCOLS, row and col instead of NX, NY, x and y.

Or if we had a two-dimensional array of planetary data for different stellar systems we might use star and planet.

#include <stdio.h>

#define NX 3
#define NY 2

int main() {
  int grid[NX][NY] = { { 1, 2 }, { 3, 4 }, { 5, 6 } };
  int x, y;

  for (x = 0; x < NX; ++x)
    for (y = 0; y < NY; ++y)
      printf("grid[%d][%d] is %d\n", x, y, grid[x][y]);

  return 0;
}
Step through this code

 

(Note that C considers a 3x2 array to be three arrays each of length two.)


We can think of a two-dimensional array either as a matrix, or a collection of vectors, or both.

More dimensions

We can do this as many times as we like. For example is we wanted to store the electric and magnetic fields as an array of length 4 with one field for each point of a three-dimensional grid we could define the four-dimensional array:

double field[NX][NY][NZ][4];

and we could treat field[NX][NY][NZ] as being the one-dimensional array (of size 4) representing the field at that point.

An N+1 dimensional array is just an array of N dimensional arrays (for N > 0).

How multidimensional arrays are stored in memory

The storage of multidimensional arrays follows immediately from their definition as "an array of arrays". As an example, we declare, say a six by two array such as:

  int iar[6][2];

The computer will store it as six one-dimensional arrays of length two, one after the other. We illustrate the start of the array storage here:

        -------------------------------------------------------------------------
Byte:   | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | 810 | 811 |
        -------------------------------------------------------------------------
Stores: | <---- iar[0][0] ----> | <---- iar[0][1] ----> | <---- iar[1][0] ----> |
        -------------------------------------------------------------------------

It follows that in the general case, if we were to declare a multiple dimensional array:

For the int array iar[M][N]; starting at location 800 the element ar[j][k] will be stored in location
800 + 4 * (N*j + k).

A multi-dimensional array is a single chunk of memory and the compiler calculates the address of an array element  using the address of the start of the array, the size of each array element and the dimensions of the array (excluding the left-hand dimension).

Note that we never need to know the left-hand dimension to find the address of an element of the array, that just tells us where to stop!

The left-hand dimension of an array is not needed to calculate the address of an array element.

Passing multidimensional arrays to functions

In a later lecture we shall see an even more flexible way of doing this using dynamic memory allocation.

When passing multi-dimensional array to functions we have to tell the function the dimensions of the array (apart from the leading dimension). This isn't a problem if we are using symbolic constants, but if they are dimensioned at run-time we have to pass the dimensions as additional arguments, before the array name in the argument list:

The following is a very simple matrix-multiply function.

/*
 * Multiply two matrices together: A = B x C
 */
// double a[nx][ny]
void matmul(int nx, int ny, int nz, double a[][ny], 
            double b[][nz], double  c[][ny]) {
  int x, y, z;

  for (x = 0; x < nx; ++x) {
    for (y = 0; y < ny; ++y) {
      a[x][y] = 0.0;
      for (z = 0; z < nz; ++z)
        a[x][y] += b[x][z] * c[z][y];
    }
  }
}
 

As always when using arrays, if we were to make a mistake and end up with the function thinking that the dimensions of the array were different from those it was originally declared with, we would be in serious trouble.

If pass an array to another function we also have to tell that function the dimensions of the array, other than the left-most one.

Summary

The text of each key point is a link to the place in the web page.

When to use arrays

Declaring and using a one-dimensional array



Passing the address of an array to a function

Arrays of arrays, or multi-dimensional arrays

Log in
                                                                                                                                                                                                                                                                       

Validate   Link-check © Copyright & disclaimer Privacy & cookies Share
Back to top