next up previous contents
Next: Review of Exception Handling Up: Slate++ Version 1.2 TUTORIAL Previous: New Features in Version   Contents

Review of C Arrays

Slate++ could, in principle, eliminate the need to ever work with standard C arrays again. However, that would require all code to be built with Slate++ from the beginning - hardly a practical solution! An alternative is to use Slate++ alongside external functions which manipulate raw C arrays, and transfer between the two representations as needed. For this reason, it's important to have an understanding of the various forms of C arrays we might encounter.

There are various ways one can specify vectors and matrices using standard C constructs. In most numerical work, the data is stored as a set of contiguous elements, regardless of how it is accessed. When interfacing between Slate++ and C arrays, we assume this is the storage method used by the external C routines. In this section, we'll describe four major methods of accessing a block of data.

The first representation is arrays of arrays. A matrix can be set up by statically allocating the array when it's initialized,

double a[5][6];
We'll call this form the 2DA for ``two-dimensional array.'' Rows (0-4) are indicated by the first index, columns (0-6) by the second. While this is a straightforward method of specifying matrices, there is no way of changing their size during program execution. Since often it's very difficult to know matrix dimensions ahead of time, and dynamic allocation of memory is much better for problems whose size may vary from run to run, the 2DA form is rarely used in computational science.

The second representation is a single array (we'll call it 1DA). A vector can be set up by statically allocating an array on initialization,

double a[30];
In this example, a is a 30-element vector with indices running from 0 to 29. Alternatively, one could imagine that while it's a one-dimensional array, it actually represents a matrix. In C, multidimensional arrays are stored in row-major order, meaning that the block of data starts with the first row, then the second, and so forth. We could explicitly calculate element (i,j) of the 1DA if we knew the row size. If we assume a represents a $5 \times 6$ matrix, element (i,j) is a[i*6 + j]. The 1DA representation is natural for vectors, and useable for matrices, but suffers from the same ailment as the 2DA form: sizes must be statically allocated (i.e., you have to know the dimensions before compiling).

A third representation is a pointer to pointers, which we'll call 2DP (``two-dimensional pointers''). This is the most common and versatile representation for matrices, and is the convention used by Numerical Recipes (Press et al.), for examples. First, a contiguous block of data is allocated (for example, using the new command). Then a pointer to a pointer is created,

double **a;
a points to an array of pointers. The first in this array of pointers points to the first element of the first row. The second element in the array of pointers points to the first element of the second row, etc. It's a bit confusing, and we recommend looking up some good web-based tutorials on pointers and arrays for a complete explanation. In the meantime, it's sufficient to know that element (i,j) can be accessed, in the 2DP representation, exactly the same way as the 2DA representation: a[i][j]. The advantage is that the matrix can be allocated dynamically (i.e., at run-time, after compilation).

The fourth representation is a pointer to the block of data, which we'll call 1DP. This is the most common representation for vectors. First, a contiguous block of data is allocated to hold the vector. Then a pointer is created,

double *a;
a points to the first element of the vector. Element (i) of the vector has the same notation as a 1DA representation: a[i]. One could also imagine the 1DA to represent a matrix, provided one knows the column size. Then, for a $5 \times 6$ matrix, element (i,j) is accessed with a[i*6 + j]. While vectors are often represented with 1DPs, matrices are most commonly represented with 2DPs instead.

A big disadvantage to using 1DPs and 2DPs is that the programmer has to explicitly keep track of all memory allocated, and ensure that when the arrays go out of scope, the memory is deallocated. If this isn't done, it's called a memory leak, and the usable RAM memory gradually diminishes until the program can no longer run. A second problem to all these native C methods is that the dimensions have to be known outside the arrays themselves. None of the above representations (1DP, 2DP, 1DA, 2DA) saves information about the dimensions of the vector or matrix, and this is something that has to be provided externally.

Slate++ solves these problems, by automatically keeping track of allocated memory, and deallocating memory when the Slate++ Vector or Matrix goes out of scope. The Slate++ objects also hold the dimensions of the Vector or Matrix, and this information can be queried. This is convenient when passing Vectors and Matrices into functions, since their dimensions don't have to be passed as additional parameters. In this tutorial, switching between Slate++ and the native C representations will be described in detail.


next up previous contents
Next: Review of Exception Handling Up: Slate++ Version 1.2 TUTORIAL Previous: New Features in Version   Contents
Brian Thorndyke 2003-11-15