DEV Community

Cover image for Pointers In C - Arrays and Strings
Srijan
Srijan

Posted on

Pointers In C - Arrays and Strings

Pointers are arguably the most difficult feature of C to understand. But, it is one of the features which make C an excellent language. This is the second article in the series. You can read the first one here.


Topics -

0. Why pointers and arrays?
1. 1-D Arrays
2. 2-D Arrays
3. Strings
4. Array of Pointers
5. Pointer to Array


0. Why pointers and arrays?

In C, pointers and arrays have quite a strong relationship. The reason they should be discussed together is what you can achieve with array notation ( arrayName[index]) can also be achieved with pointers, generally faster.

1. 1-D Arrays

Let us look at what happens when we write int myArray[5];.

Five consecutive blocks of memory starting from myArray[0] to myArray[4] are created with garbage values in them. Each of the blocks is of size 4 bytes.

Thus, if the address of myArray[0] is 100 (say), the address of the rest of the blocks would be 104, 108, 112, and 116.

Now, have a look at the following code -

int prime[5] = {2,3,5,7,11};
printf("Result using &prime = %d\n",&prime);
printf("Result using prime = %d\n",prime);
printf("Result using &prime[0] = %d\n",&prime[0]);

/* Output */
Result using &prime = 6422016
Result using prime = 6422016
Result using &prime[0] = 6422016

So, &prime, prime, and &prime[0] all give the same address, right? Well, wait and read because you are in for a surprise (and confusion). Let's try to increment each of &prime, prime, and &prime[0] by 1.

printf("Result using &prime = %d\n",&prime + 1);
printf("Result using prime = %d\n",prime + 1);
printf("Result using &prime[0] = %d\n",&prime[0] + 1);

/* Output */
Result using &prime = 6422036
Result using prime = 6422020
Result using &prime[0] = 6422020

Wait! How come &prime + 1 results to something different than the other two? And why prime + 1 and &prime[0] + 1 are still equal? Let's answer these questions.

1.prime and &prime[0], both point to the 0th element of the array prime. Thus, name of an array is itself a pointer to the 0th element of the array.

Here, both point to the first element of size 4 bytes. When you add 1 to them, they now point to the 1st element in the array. Therefore an increase in the address by 4.

2.&prime on the other hand is a pointer to an int array of size 5. It stores the base address of the array prime[5], which is equal to the address of the first element. However, increase by 1 to it results in an address with an increase of 5 x 4 = 20 bytes.

In short, arrayName and &arrayName[0] point to the 0th element whereas &arrayName points to the whole array.

We can access the array elements using subscripted variables like this -

int prime[5] = {2,3,5,7,11};
for( int i = 0; i < 5; i++)
{
  printf("index = %d, address = %d, value = %d\n", i, &prime[i], prime[i]);
}

We can do the same using pointers which are always faster than using subscripts.

int prime[5] = {2,3,5,7,11};
for( int i = 0; i < 5; i++)
{
  printf("index = %d, address = %d, value = %d\n", i, prime + i, *(prime + i));
}

Both methods give the output -

index = 0, address = 6422016, value = 2
index = 1, address = 6422020, value = 3
index = 2, address = 6422024, value = 5
index = 3, address = 6422028, value = 7
index = 4, address = 6422032, value = 11

Thus, &arrayName[i] and arrayName[i] are same as arrayName + i and *(arrayName + i) respectively.

2. 2-D Arrays

Two-dimensional arrays are an array of arrays.

int marks[5][3] = { { 98, 76, 89},
                    { 81, 96, 79},
                    { 88, 86, 89},
                    { 97, 94, 99},
                    { 92, 81, 59}
                  };

Here, marks can be thought of as an array of 5 elements, each of which is a one-dimensional array containing 3 integers. Let us work through a series of programs to understand different subscripted expressions.

printf("Address of whole 2-D array = %d\n", &marks);
printf("Addition of 1 results in %d\n", &marks +1);

/* Output */
Address of whole 2-D array = 6421984
Addition of 1 results in 6422044

Like 1-D arrays, &marks points to the whole 2-D array, marks[5][3]. Thus, increment to it by 1 ( = 5 arrays X 3 integers each X 4 bytes = 60) results in increment by 60 bytes.

printf("Address of 0th array = %d\n", marks);
printf("Addition of 1 results in %d\n", marks +1);
printf("Address of 0th array =%d\n", &marks[0]);
printf("Addition of 1 results in %d\n", &marks[0] + 1);

/* Output */
Address of 0th array = 6421984
Addition of 1 results in 6421996
Address of 0th array = 6421984
Addition of 1 results in 6421996

If marks was a 1-D array, marks and &marks[0] would have pointed to the 0th element. For a 2-D array, elements are now 1-D arrays. Hence, marks and &marks[0] point to the 0th array (element), and the addition of 1 point to the 1st array.

printf("Address of 0th element of 0th array = %d\n", marks[0]);
printf("Addition of 1 results in %d\n", marks[0] + 1);
printf("Address of 0th element of 1st array = %d\n", marks[1]);
printf("Addition of 1 results in %d\n", marks[1] + 1);

 /* Output */
Address of 0th element of 0th array = 6421984
Addition of 1 results in 6421988
Address of 0th element of 1st array = 6421996
Addition of 1 results in 6422000

And now, comes the difference. For a 1-D array, marks[0] would give the value of the 0th element. An increment by 1 would increase the value by 1.

But, in a 2-D array, marks[0] points to the 0th element of the 0th array. Similarly, marks[1] points to the 0th element of the 1st array. An increment by 1 would point to the 1st element in the 1st array.

printf("Value of 0th element of 0th array = %d\n", marks[0][0]);
printf("Addition of 1 results in %d", marks[0][0] + 1);

/* Output */
Value of 0th element of 0th array = 98
Addition of 1 results in 99

This is the new part. marks[i][j] gives the value of the jth element of the ith array. An increment to it changes the value stored at marks[i][j]. Now, let us try to write marks[i][j] in terms of pointers.

We know marks[i] + j would point to the ith element of the jth array from our previous discussion. Dereferencing it would mean the value at that address. Thus, marks[i][j] is equal to *(marks[i] + j).

From our discussion on 1-D arrays, marks[i] is the same as *(marks + i). Thus, marks[i][j] can be written as *(*(marks + i) + j) in terms of pointers.

Here is a summary of notations comparing 1-D and 2-D arrays.

Expression 1-D Array 2-D Array
&ArrayName points to the address of whole array, adding 1 increases the address by 1 x sizeof(ArrayName) points to the address of whole array, adding 1 increases the address by 1 x sizeof(ArrayName)
ArrayName points to the 0th element, adding 1 increases the address to 1st element points to the 0th element (array), adding 1 increases the address to 1st element (array)
&ArrayName[i] points to the the ith element, adding 1 increases the address to (i+1)th element points to the ith element (array), adding 1 increases the address to the (i+1)th element (array)
ArrayName[i] gives the value of the ith element, adding 1 increases the value of the ith element points to the 0th element of the ith array, adding 1 increases the address to 1st element of the ith array
ArrayName[i][j] Nothing gives the value of the jth element of the ith array, adding 1 increases the value of the jth element of the ith array
Pointer Expression To Access The Elements *(ArrayName + i) *(*(ArrayName + i) + j)

3. Strings

A string is a one-dimensional array of characters terminated by a null(\0). When we write char name[] = "Srijan";, each character occupies one byte of memory with the last one always being \0.

Similar to the arrays we have seen, name and &name[0] points to the 0th character in the string, while &name points to the whole string. Also, name[i] can be written as *(name + i).

/* String */
char champions[] = "Liverpool";

printf("Pointer to whole string = %d\n", &champions);
printf("Addition of 1 results in %d\n", &champions + 1);

/* Output */
Address of whole string = 6421974
Addition of 1 results in 6421984

printf("Pointer to 0th character = %d\n", &champions[0]);
printf("Addition of 1 results in %d\n", &champions[0] + 1);

/* Output */
Address of 0th character = 6421974
Addition of 1 results in a pointer to 1st character 6421975

printf("Pointer to 0th character = %d\n", champions);
printf("Addition of 1 results in a pointer to 1st character %d\n", champions + 1);

/* Output */
Address of 0th character = 6421974
Addition of 1 results in 6421975

printf("Value of 4th character = %c\n", champions[4]);
printf("Value of 4th character using pointers = %c\n", *(champions + 4));

/* Output */
Value of 4th character = r
Value of 4th character using pointers = r

A two-dimensional array of characters or an array of strings can also be accessed and manipulated as discussed before.

/* Array of Strings */
char top[6][15] = {
                    "Liverpool",
                    "Man City",
                    "Man United",
                    "Chelsea",
                    "Leicester",
                    "Tottenham"
                  };

printf("Pointer to 2-D array = %d\n", &top);
printf("Addition of 1 results in %d\n", &top + 1);

 /* Output */
Pointer to 2-D array = 6421952
Addition of 1 results in 6422042

printf("Pointer to 0th string = %d\n", &top[0]);
printf("Addition of 1 results in %d\n", &top[0] + 1);

 /* Output */
Pointer to 0th string = 6421952
Addition of 1 results in 6421967

printf("Pointer to 0th string = %d\n", top);
printf("Addition of 1 results in %d\n", top + 1);

 /* Output */
Pointer to 0th string = 6421952
Addition of 1 results in 6421967

printf("Pointer to 0th element of 4th string = %d\n", top[4]);
printf("Pointer to 1st element of 4th string = %c\n", top[4] + 1);

 /* Output */
Pointer to 0th element of 4th string = 6422012
Pointer to 1st element of 4th string = 6422013

printf("Value of 1st character in 3rd string = %c\n", top[3][1]);
printf("Same using pointers = %c\n", *(*(top + 3) + 1));

 /* Output */
Value of 1st character in 3rd string = h
Same using pointers = h

4. Array of Pointers

Like an array of ints and an array of chars, there is an array of pointers as well. Such an array would simply be a collection of addresses. Those addresses could point to individual variables or another array as well.

The syntax for declaring a pointer array is -

dataType *variableName[size];

/* Examples */
int *example1[5];
char *example2[8];

Following operators precedence, the first example can be read as - example1 is an array([]) of 5 pointers to int. Similarly, example2 is an array of 8 pointers to char.

We can store the two-dimensional array to string top using pointer array and save memory as well.

char *top[] = {
                    "Liverpool",
                    "Man City",
                    "Man United",
                    "Chelsea",
                    "Leicester",
                    "Tottenham"
                  };

top will be containing the base addresses of all the respective names. The base address of "Liverpool" will be stored in top[0], "Man City" in top[1], and so on.

In the earlier declaration, we required 90 bytes to store the names. Here, we only require ( 58 (sum of bytes of names) + 12 ( bytes required to store the address in the array) ) 70 bytes.

The manipulation of strings or integers becomes a lot easier when using an array of pointers.

If we try to put "Leicester" ahead of "Chelsea", we just need to switch the values of top[3] and top[4] like below -

char *temporary;
temporary = top[3];
top[3] = top[4];
top[4] = temporary;

Without pointers, we would have required to exchange every character of the strings, which would have taken more time. That's why strings are generally declared using pointers.

5. Pointer to Array

Like "pointer to int" or "pointer to char", we have pointer to array as well. This pointer points to whole array rather than its elements.

Remember we discussed how &arrayName points to the whole array? Well, it is a pointer to array.

A pointer to array can be declared like -

dataType (*variableName)[size];

/* Examples */
int (*ptr1)[5];
char (*ptr2)[15];

Notice the parentheses. Without them, these would be an array of pointers. The first example can be read as - ptr1 is a pointer to an array of 5 int(integers).

int goals[] = { 85,102,66,69,67};
int (*pointerToGoals)[5] = &goals;
printf("Address stored in pointerToGoals %d\n", pointerToGoals);
printf("Dereferncing it, we get %d\n",*pointerToGoals);

/* Output */
Address stored in pointerToGoals 6422016
Dereferencing it, we get 6422016

When we dereference a pointer, it gives the value at that address. Similarly, by dereferencing a pointer to array, we get the array and the name of the array points to the base address. We can confirm that *pointerToGoals gives the array goals if we find its size.

printf("Size of goals[5] = %d, *pointerToGoals);

/* Output */
Size of goals[5] = 20

If we dereference it again, we will get the value stored in that address. We can print all the elements using pointerToGoals.

for(int i = 0; i < 5; i++)
printf("%d ", *(*pointerToGoals + i));

/* Output */
85 102 66 69 67

Pointers and pointer to arrays are quite useful when paired up with functions. We will discuss them in the next one.

Latest comments (0)