CodingBison

Pointers can be easily mapped to an array and can be used to navigate the array elements. Once mapped, navigating to an element of the array is as simple as incrementing the pointer! The mapping works because an array uses a contiguous piece of memory. Thus, if we have a pointer pointing to the first element of the array, then we can increment the pointer to refer to the next element, and so on.

For example, the following code uses a pointer, ptr_to_array_x to point to the first element of an array, array_x.

 int array_x[100];
 int *ptr_to_array_x;

 /* ptr_to_array_x points to the first element of the array */
 ptr_to_array_x = &array_x[0];

 /* Or, even better! */
 ptr_to_array_x = array_x;

Since ptr_to_array_x points to the first element, *ptr_to_array_x is same as array_x[0]. If we increment the pointer, then it points to the second element; thus, *(++ptr_to_array_x) would be same as array_x[1], and so on.

Let us now see an example (provided below) that shows two ways to print elements of this array -- one by advancing the array index and the other by incrementing a pointer. We can point a pointer to the next element of the array by incrementing it as: "ptr_to_array_x++". Since ptr_to_array_x is of type int, increasing the pointer automatically moves the pointer to the location of the next int of the array by advancing it by size of an integer.

 #include <stdio.h>

 #define TOTAL_ELEMENTS 5

 int main () {
     int arr_x[TOTAL_ELEMENTS] = {1, 2, 3, 4, 5};
     int *ptr_to_arr_x; 
     int i;

     /* Traverse the array using its index */
     printf("Let us print using array index\n");
     for (i = 0; i < TOTAL_ELEMENTS; i++) {
         printf("Value: %d Address: %p\n", arr_x[i], &arr_x[i]);
     }   

     /* Traverse the array using a pointer */
     printf("\nLet us print using a pointer\n");
     ptr_to_arr_x = arr_x;
     for (i = 0; i < TOTAL_ELEMENTS; i++) {
         printf("Value: %d Address: %p\n", *ptr_to_arr_x, ptr_to_arr_x);
         ptr_to_arr_x++;
     }
     return 0;
 }

Note that "ptr = ++ptr_to_array_x" is not the same as "y = ++(*ptr_to_array_x)" -- the former means incrementing the address referred to by the pointer and assigning it to a new pointer, ptr. The the latter means increasing the value pointed to by the pointer, ptr_to_array_x and assigning to variable y. You might also run into yet another variation: "y = *++ptr_to_array_x" or "y = *(++ptr_to_array_x)" -- this means that we first increment the pointer and then read the value pointed by it. It is a good idea to use parenthesis,s o that the code becomes more readable.

The output shows that the value and address of each element is identical for both cases. As expected, the output shows that any two consecutive addresses of the array differ by a value of 4, which is the size of an integer; we use a 32-bit machine to compile the code.

 Let us print using array index
 Value: 1 Address: 0xbf936e24
 Value: 2 Address: 0xbf936e28
 Value: 3 Address: 0xbf936e2c
 Value: 4 Address: 0xbf936e30
 Value: 5 Address: 0xbf936e34

 Let us print using a pointer
 Value: 1 Address: 0xbf936e24
 Value: 2 Address: 0xbf936e28
 Value: 3 Address: 0xbf936e2c
 Value: 4 Address: 0xbf936e30
 Value: 5 Address: 0xbf936e34

In the end, a word of caution about using sizeof() function when we refer to arrays as pointers! We should not the sizeof operator on painting_array pointers since it would not give us the size of the entire array. Instead, being a pointer, the sizeof() would return the size of the pointer (4 bytes on 32-bit machines)! Also, when we pass arrays to functions, we actually pass a pointer. Once again, we should not use the sizeof() in the called function since the array is actually as a pointer. For such cases, we should explicitly pass the length of the array as a function parameter.

Pointers and Strings

Since C stores strings as array of characters, we can also use pointers to access individual characters in a string. Let us use an example of copying a string to demonstrate accessing elements of a character array (a string, actually) by merely incrementing the pointers.

In the example provided below, the char pointers ptr_name and ptr_copy_name point to the two char arrays. Using a while loop, we assign values from ptr_name to ptr_copy_name, one character at a time, and then increment both pointers to point to next character. Since, the pointers are of type "char *", every time we increment these counters, the new address advances by size of one char. We terminate the while loop when the value of character pointed by ptr_name become the NUL terminator character ('\0').

 #include <stdio.h>

 int main () {
     char name[25] = "Leonardo da Vinci";
     char copy_name[25] = "Van Gogh";
     char *ptr_name = NULL;
     char *ptr_copy_name = NULL;

     ptr_name = name;
     ptr_copy_name = copy_name;

     printf("Before copying: \n");
     printf("ptr_name: %20s ptr_copy_name: %15s\n", ptr_name, ptr_copy_name);
     printf("    name: %20s     copy_name: %15s\n", name, copy_name);

     while ( (*(ptr_copy_name++) = *(ptr_name++)) != '\0') ;

     printf("\nAfter copying: \n");
     printf("ptr_name: %20s ptr_copy_name: %15s\n", ptr_name, ptr_copy_name);
     printf("    name: %20s     copy_name: %15s\n", name, copy_name);
     return 0;
 }

The output (provided below) shows that once we make the pointer point to a string and print it, the output is identical whether we print the string itself or the pointer. Lastly, after copying, we find that both ptr_name and ptr_copy_name are NULL -- this is because at the the end copying, both strings have advanced till the end of ptr_name and hence, they both now point to '\0'.

 Before copying: 
 ptr_name:    Leonardo da Vinci ptr_copy_name:        Van Gogh 
     name:    Leonardo da Vinci     copy_name:        Van Gogh 

 After copying: 
 ptr_name:                      ptr_copy_name:                 
     name:    Leonardo da Vinci     copy_name: Leonardo da Vinci 

In the above example, since the while clause is executed first and the check for the NUL character is done later, the example also copies the last NUL character. We should also note that the copy_name string has enough storage (25 bytes) to accommodate the "Leonardo da Vinci" string. If it were to have less storage, then we would end up over-running some of the buffer. As a general rule, when dealing with strings, it is important to pay extra attention to the storage size and the NUL character.

Creating Arrays using malloc()

Being able to define an array with a given size is nice, but sometimes we do not know the array size in advance. For such cases, a declaration of array specifying its initial size, this approach would not work. Instead, we can use a malloc() call to allocate the array on the fly. We can use malloc() to allocate both one-dimensional and two-dimensional arrays. As with every malloc() calls, we need to make sure that we free() the memory once we are done!

With that, let us look at a handful of examples that use malloc() calls to create one-dimensional and two-dimensional arrays respectively.

The first example uses malloc to allocate a one-dimensional array. It starts by defining an array as "int* painting_array"; this works since arrays can be also be referenced as pointers. The example then assigns values to array elements and prints them. In the end, it frees the allocated memory using the free() call.

 #include <stdio.h>
 #include <stdlib.h>

 #define NUM_OF_COLUMNS 4

 int main () {
     int i;
     int* painting_array;

     /* Allocate the array */
     painting_array = (int *)malloc(sizeof(int) * NUM_OF_COLUMNS);
     if (!painting_array) {
         fprintf(stderr, "Malloc failed \n");
         return -1;
     }

     /* Assign values to array elements */
     painting_array[0] = 1000;
     painting_array[1] = 1001;
     painting_array[2] = 1002;
     painting_array[3] = 1003;

     for (i = 0; i < NUM_OF_COLUMNS; i++) {
         printf("[i: %d] painting id: %d (Address: %p)\n", 
             i, painting_array[i], &painting_array[i]);
     }

     /* Free the allocated array */
     free(painting_array);
     return 0;
 }

As expected, the output prints all the elements of the array. Also, looking at the addresses, elements of the array form a contiguous block of memory. If you notice something unusual about these addresses (they are not exactly 4 bytes), then that is okay since we cannot make any assumption about the address of the block returned from a malloc call.

 [i: 0] painting id: 1000 (Address: 0x90a3008)
 [i: 1] painting id: 1001 (Address: 0x90a300c)
 [i: 2] painting id: 1002 (Address: 0x90a3010)
 [i: 3] painting id: 1003 (Address: 0x90a3014)

Our second example uses malloc to allocate a two-dimensional array. It starts by defining an array as "int** painting_array" -- this means that it is a pointer to a pointer. How does that translate to an two-dimensional array? Well, the first pointer defines all the rows of the array and each row is identified by yet another pointer. This works since a two-dimensional array is nothing but an array of one-dimensional arrays.

The example then allocates a pointer that represents the rows of the painting_array. Next, it uses malloc to build individual elements of the painting_array row. In the end, it frees the allocated memory using the free() call.

Note the ordering of the free() calls is opposite to that of the malloc() calls. We allocate the painting_array first and then allocate the rows. With free(), we first free the rows, and then free the painting_array. Needless to say, if we free the painting_array pointer first, then would loose the handle to all rows and we would end up with a memory leak!

 #include <stdio.h>
 #include <stdlib.h>

 #define NUM_OF_ROWS 2
 #define NUM_OF_COLUMNS 4

 int main () {
     int i, j;
     int **painting_array; 

     /* Let us use malloc to create rows first */
     painting_array = (int** )malloc(sizeof(int*) * NUM_OF_ROWS);
     if (!painting_array) {
         fprintf(stderr, "Malloc failed\n");
         goto AllDone;
     }
     /* Next, let us allocate one-dimensional arrays (rows) for each column */
     for (i=0; i < NUM_OF_ROWS; i++) {
         painting_array[i] = (int *)malloc(sizeof(int) * NUM_OF_COLUMNS);
         if (!painting_array[i]) {
             fprintf(stderr, "Malloc failed\n");
             goto AllDone;
         }
     }

     /* Painting Unique IDs */
     painting_array[0][0] = 1000;
     painting_array[0][1] = 1001;
     painting_array[0][2] = 1002;
     painting_array[0][3] = 1003;

     /* Number of paintings available in the store */
     painting_array[1][0] = 100;
     painting_array[1][1] = 10;
     painting_array[1][2] = 10;
     painting_array[1][3] = 11;

     for (i=0; i < NUM_OF_COLUMNS; i++) {
         printf("[i: %d] painting id (%p): %d Availability (%p): %d\n",
             i, &painting_array[0][i], painting_array[0][i], 
             &painting_array[1][i], painting_array[1][i]);
     }

 AllDone:
     /* Free the allocated columns for each row */
     for (i=0; i < NUM_OF_ROWS; i++) {
         if (painting_array[i]) {
             free(painting_array[i]);
         }
     }
     if (painting_array) {
         free(painting_array); 
     }
     return 0;
 }

With the output, note that each of the row use a contiguous piece of memory. This is understandable since we use a separate malloc call for each of the two rows.

 [i: 0] painting id (0x8528018): 1000 Availability (0x8528030): 100
 [i: 1] painting id (0x852801c): 1001 Availability (0x8528034): 10
 [i: 2] painting id (0x8528020): 1002 Availability (0x8528038): 10
 [i: 3] painting id (0x8528024): 1003 Availability (0x852803c): 11

Lastly, when C defines a multi-dimensional arrays, it allocates a contiguous chunk of memory such that it can accommodate all the elements. Thus, if we have a 2 dimensional array with k rows and m columns, then the address of each row would be continuous and the address of the element starting the next row would be immediately after the address of the last element in the previous row.

Before we wrap up this page, let us confirm this using a simple example. The example defines a 2-dimensional array and then prints the address of each element row by row. Here it is:

 #include <stdio.h>

 #define NUM_OF_ROWS 2
 #define NUM_OF_COLUMNS 4

 int main () {
     int painting_array[NUM_OF_ROWS][NUM_OF_COLUMNS];
     int i, j;

     /* Let us print the addresses of elements */
     for (i=0; i < NUM_OF_ROWS; i++) {
         for (j=0; j < NUM_OF_COLUMNS; j++) {
             printf("\t %p ", &painting_array[i][j]);
         }
         printf("\n");
     }
     return 0;
 }

And, here is the output:

 	 0xbffb6ae8 	 0xbffb6aec 	 0xbffb6af0 	 0xbffb6af4 
 	 0xbffb6af8 	 0xbffb6afc 	 0xbffb6b00 	 0xbffb6b04 




comments powered by Disqus