Pointers and Arrays in C

As anyone who had programmed in C knows, pointers can be (also) used to access the data of an array.

From the Wikipedia article about C language:

C supports the use of pointers, a very simple type of reference that records, in effect, the address or location of an object or function in memory. Pointers can be dereferenced to access data stored at the address pointed to, or to invoke a pointed-to function. Pointers can be manipulated using assignment and also pointer arithmetic. The run-time representation of a pointer value is typically a raw memory address (perhaps augmented by an offset-within-word field), but since a pointer’s type includes the type of the thing pointed to, expressions including pointers can be type-checked at compile time. Pointer arithmetic is automatically scaled by the size of the pointed-to data type.


1-dimensional array

In an 1-dimensional array things are quite simple. For example:

int ar[5] = {1, 2, 3, 4, 5};

declares a 5 element integer array, where ar is a pointer to the beginning of the continuous memory location where the 5 integers are stored. So, ar is simply a a pointer to an int that points to the memory location of ar[0] item, thus ar == &ar[0].

Now, what is important to understand is what happens when we have the following code: (ar + 1)

What happens is because we have declared ar as a pointer to an int, the compiler will actually do the following addition:

(ar + 1*sizeof(int)) == (memory location that ar points) + (the size of 1 integer) == &ar[1] == the memory location of the next integer after the one at ar

So, we can easily see that *(ar + 1) == ar[1], since (ar + 1) == &ar[1] (pointer dereferencing).

So, if we want to simply print all the items of the array we can use the following two ways:


int i;
for (i = 0; i < ARRAY_SIZE; i++)
 printf("%d\n", ar[i]);
for (i = 0; i < ARRAY_SIZE; i++)
 printf("%d\n", *(ar + i));

That was simple!

2-dimensional array

Lets continue with a 2-dimensional array. For example:

int ar[2][3] = {{1,2,3}, {4, 5, 6}};

Here things are more complicated. Lets take it one by one:

ar is again a pointer that points at the first element of the array. But this time the first element of the array is an array (3 int item array), so ar is not a pointer to an integer, but a pointer to an array of integers and to be precise a pointer to 3 consecutive ints. To make this clear, just see that ar[j], j={0, 1} are 3 int arrays.

Now, since ar is a pointer to an array, we can understand that ar is a pointer to a pointer. Lets try to find some “equalities”:

  • as we said, ar points to the first array item (which happens to be also an array), so ar == &ar[0]. ar[0] is a pointer to the first element of the (inner) array, so ar[0] == &ar[0][0]. Since both the integer (ar[0][0]) and the array of 3 ints (ar[0]) begin on the same memory location, we have that ar == ar[0].
  • the big difference lies when we want to add to the pointers in order to reference other array items. As we saw before, adding 1 to a pointer, increases the value by the size of the referenced item. So, (ar + 1) != (ar[0] + 1), because in the first case the size of the 3-int array is added, while in the second case the size of int is added.
  • Dereferencing a pointer or address (either by applying the * operator or the [ ] operator with an index) returns the value represented by the referred-to object. So, same as the 1-dimensional array example, *ar[0] == a[0][0]. Now, the *ar dereferencing gives the value contained in the ar[0], which is also a pointer to the memory &ar[0][0]. So, *ar == &ar[0][0] and thus **ar == *(&ar[0][0]) == ar[0][0] == *ar[0].

Having a pointer of a pointer, hence the address of an address, is called double indirection.

Some fetching examples:

  • ar[1] == *(ar + 1)
  • ar[1][2] == *(ar[1] + 2) == *(*(ar + 1) + 2)

Smooth..

N-dimensional arrays

What happens on N-dimensional arrays, when N > 2, is out of the scope of the article.. Just kidding, what happens follows exactly the same philosophy as in the 2-dimensional example. If N = 3, for example:

  • ***ar == **ar[0] == *ar[0][0] == *(&ar[0][0][0])
  • and, ar[1] == *(ar + 1)
  • ar[1][2] == *(ar[1] + 2) == *(*(ar + 1) + 2)
  • ar[1][2][3] == *(ar[1][2] + 3) == *(*(*(ar + 1) + 2) + 3)

That’s it, nice (?) and simple (?). Enjoy!

Here is a program that prints all (un)equalities that I talked about:

#include 
 
#define ARRAY_SIZE 5
 
int main() {
  //1-dimensional array
  int ar[ARRAY_SIZE] = {1, 2, 3, 4, 5};
  printf("ar = %p, &ar[0] = %p\n", ar, &ar[0]);
 
  printf("(ar + 1) = %p, &ar[1] = %p\n", (ar + 1), &ar[1]);
  printf("*(ar + 1) = %d, ar[1] = %d\n", *(ar + 1), ar[1]);
 
  int i;
  for (i = 0; i < ARRAY_SIZE; i++) {
    printf("%d,", ar[i]);
    printf("%d ", *(ar + i));
  }
  printf("\n");
 
  //2-dimensional array
  int ar2[2][3] = {{1,2,3}, {4, 5, 6}};
 
  printf("ar2 = %p, &ar2[0] = %p\n", ar2, &ar2[0]);
  printf("ar2[0] = %p, &ar2[0][0] = %p\n", ar2[0], &ar2[0][0]);
  printf("(ar2 + 1) = %p, (ar2[0] + 1) = %p\n", (ar2 + 1), (ar2[0] + 1));
  printf("(ar2 + 1) - (ar2[0] + 1) = %X, 2*sizeof(int) = %d\n",
    (int)(ar2 + 1) - (int)(ar2[0] + 1), 2*sizeof(int));
  printf("*ar2[0] = %d, ar2[0][0] = %d\n", *ar2[0], ar2[0][0]);
  printf("*ar2 = %p, &ar2[0] = %p\n", *ar2, &ar2[0]);
  printf("**ar2 = %d, *(&ar2[0][0]) = %d\n", **ar2, *(&ar2[0][0]));
 
  //N-dimentional arrays, N = 3
  int ar3[3][2][2] = {{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}, {{9, 10}, {11, 12}}};
 
  printf("***ar3 = %d, **ar3[0] = %d, *ar3[0][0] = %d, *(&ar3[0][0][0]) = %d\n",
    ***ar3, **ar3[0], *ar3[0][0], *(&ar3[0][0][0]));
  printf("ar3[1] = %p, *(ar3 + 1) = %p\n", ar3[1], *(ar3 + 1));
  printf("ar3[1][2] = %p, *(ar3[1] + 2) = %p, *(*(ar3 + 1) + 2) = %p\n",
    ar3[1][2], *(ar3[1] + 2), *(*(ar3 + 1) + 2));
  printf("ar3[1][2][2] = %d, *(ar3[1][2] + 2) = %d, *(*(*(ar3 + 1) + 2) + 2) = %d\n",
    ar3[1][2][2], *(ar3[1][2] + 2), *(*(*(ar3 + 1) + 2) + 2));
 
  return 0;
}

That produced the following output in my machine:

ar = 0xbfdf65d8, &ar[0] = 0xbfdf65d8
(ar + 1) = 0xbfdf65dc, &ar[1] = 0xbfdf65dc
*(ar + 1) = 2, ar[1] = 2
1,1 2,2 3,3 4,4 5,5
ar2 = 0xbfdf65c0, &ar2[0] = 0xbfdf65c0
ar2[0] = 0xbfdf65c0, &ar2[0][0] = 0xbfdf65c0
(ar2 + 1) = 0xbfdf65cc, (ar2[0] + 1) = 0xbfdf65c4
(ar2 + 1) - (ar2[0] + 1) = 8, 2*sizeof(int) = 8
*ar2[0] = 1, ar2[0][0] = 1
*ar2 = 0xbfdf65c0, &ar2[0] = 0xbfdf65c0
**ar2 = 1, *(&ar2[0][0]) = 1
***ar3 = 1, **ar3[0] = 1, *ar3[0][0] = 1, *(&ar3[0][0][0]) = 1
ar3[1] = 0xbfdf65a0, *(ar3 + 1) = 0xbfdf65a0
ar3[1][2] = 0xbfdf65b0, *(ar3[1] + 2) = 0xbfdf65b0,
  *(*(ar3 + 1) + 2) = 0xbfdf65b0
ar3[1][2][2] = 11, *(ar3[1][2] + 2) = 11, *(*(*(ar3 + 1) + 2) + 2) = 11

Leave a Reply

*