Since a pointer to type T is analogous to an array of type T, a pointer to a pointer (T**) is analogous to an array of type T*, i. e., an array of pointers to type T. Each element in this array of pointers can be used further to point to an array of type T. Thus, a pointer to a pointer can be used to represent two-dimensional arrays, as illustrated in Fig.
Observe that we have a lot of flexibility in allocating memory for two-dimensional arrays. Thus, we can allocate a rectangular matrix (Fig a), a lower/upper triangular matrix (Fig b) and a ragged matrix in which each row can have different number of elements (Fig c). The use of a pointer to a pointer in conjunction with dynamic memory allocation is advantageous over a static matrix as it allows the allocation of a two-dimensional array of desired size and shape, avoiding wastage of memory.
Note that when a pointer to a pointer is declared, the memory is allocated only for the pointer variable. The memory for the pointer array and the two-dimensional array elements is usually allocated separately using dynamic memory allocation functions, namely, malloc or calloc.
A simplified function imat_alloc given below allocates a regular integer matrix of size m x n to variable tmp of type int ** and returns this pointer to the calling function
int **imat_alloc(int m, int n)
{
int i;
int **tmp = (int**) malloc(m * sizeof(int *));
for(i = 0; i < m; i++)
tmp[i] = (int*) malloc(n * sizeof(int));
return tmp;
}
Note that the function return type is a pointer to a pointer to int, i.e., int ** and the function returns a value of correct type (as tmp is of type int ** ). First, malloc allocates memory form pointers (i. e., m* sizeof (int *)) and assigns the address of this block, after typecasting to int **, to variable tmp. Then a for loop is used to allocate memory for each row. The malloc function now allocates memory for n integers (i.e., n * sizeof (inti) and assigns the address of this block, after typecasting to int *, to the ith pointer tmp[i] . Finally, the value of tmp is returned.
As you have probably noticed, the error checking code has been omitted in imat_alloc function to keep the things simple and focus on memory allocation logic. Although this is okay for small programs, we should include the error handling code in actual applications. The modified function is given below.
/* allocate the memory for a dynamic matrix */
int **imat_alloc(int m, int n)
{
int i;
/* first allocate memory for pointer array */
int **tmp = (int**) malloc(m * sizeof(int *));
if (tmp == NULL) {
printf(“Error: Out of memory …\n”);
exit(1);
}
/* allocate memory for matrix rows */
for(i = 0; i < m; i++) {
tmp[i] = (int*) malloc(n * sizeof(int));
if (tmp[i] ==NULL) {
printf (“Error: Out of memory …\n”);
exit (1);
}
}
return tmp;
}
This function can be called from any function to allocate a matrix as shown below.
int **a;
a= imat_alloc(m, n);
Note that now we cannot simply use the free function (as in free (a))to free the memory allocated to a matrix as it will only free the memory allocated to the pointer array. The function imat_free to free an integer matrix is given below. It accepts two parameters: a pointer to the matrix and the number of rows. It first uses a for loop to free the memory allocated to matrix rows and then frees the memory allocated to the pointer array.
void imat_free(int **a, int m)
{
int i;
/* free the memory allocated to matrix rows */
for (i = 0; i < m; i++)
free(a[i]);
free(a); /* free the memory allocated to pointer array */
}
Illustrates pointers to one- and two-dimensional arrays
#include <stdio.h>
void main()
{
int i, j , k;
int Array[2] [3] = {2,3,4,5,6,7 };
int (*pArray) [3] = Array ;
int Bray[4] = {10, 11, 12, 13};
int *pBray = Bray;
for ( k=0 ; k<4; k++)
printf("Bray[%d] = %d\n", k, *(pBray + k ));
for ( i=0 ; i<2; i++)
for ( j =0; j<3; j++)
{
printf("Array[%d] [%d] = %d ", i, j, *(*(Array +i)+j));
printf(",\tpArray[%d] [%d] = %d\n", i, j, *(*(pArray +i)+j));
}
}