Mastering the art of pointers

Mastering the art of pointers

A clear and concise guide to understanding and using pointers

Table of contents

No heading

No headings in the article.

For any new programmer starting their journey with a language that is statically typed like C / C++, pointers can be pretty hard to understand. This roadblock can often discourage newbies from learning altogether. However, it doesn't have to be that way. Continue reading to find out how I overcame my fear of pointers.

So what are pointers? Speaking in layman's terms pointers are variables that contain the memory address of another variable as its value. So quite simply put, accessing a pointer is equivalent to accessing the value of a variable that is stored in the location contained in the pointer.

Here, in the picture above, the pointer ptr contains the value 0x7fffa0757dd4 which is the address of the variable Var. So ptr is pointing to the memory location of Var. To make things easier, consider a situation where you don't know how to reach a particular shop and ask a passerby for directions. The shop you are trying to reach is Var and the person giving you directions is ptr.

Pointers in C

  • Representation - A pointer variable points to a data type (like int) of the same type, and is created with the * operator.

    In the code snippet below we create a pointer variable with the name ptr, which points to an int variable (my_age). Keep in mind that the type of the pointer has to match the type of the variable you're working with (int in this case). The & operator stores the memory address of the my_age variable and assigns it to the pointer ptr. Now, ptr holds the value of my_age's memory address.

  •                 int my_age = 21; // An int variable
                    int *ptr = &my_age; //pointer variable ptr which stores address of my_age
                    //&my_age gives the address of my_age variable
    
  • Reference and Dereference - using a pointer to get the memory address of a variable is called reference. Accessing the value of a variable using a pointer is called dereference. Dereferencing is commonly done with a * operator.

  •                 printf("%p\n", ptr); //referencing- outputs address of my_age
                    printf("%d\n", *ptr);//dereferencing -outputs value of my_age
    
  • Type of a pointer - the datatype of a pointer has to match the datatype of the variable it is pointing to. An integer pointer cannot point to a variable which is a character.

  •             int var = 10; //int variable
                int *ptr; //int pointer
                ptr = &var; //int pointer pointing to address of int variable
                char var2 = 'b'; //char variable
                char *ptr2; //char pointer 
                ptr2 = &var2 //char pointer pointing to address of char variable
    
  •             ptr = &var2 //error, int pointer cannot point to char variable
                ptr2 = &var1 //error, char pointer cannot point to int variable
    

Array pointers

Pointers that contain location of (or point to) the starting address of an array are called array pointers. Array pointers have to match the datatype of the content in the array.

int arr[5] = {1, 2, 3, 4, 5};
int *ptr;
ptr = arr; //ptr points to zeroth element of array
printf("%p\n", ptr); //outputs 0x7fff4f32fd50
printf("%p\n", &arr[0]); //outputs 0x7fff4f32fd50, ptr points to start addr of array 
printf("%d", *ptr); //outputs 1, first element of arr

Pointer ptr in the example above points to the start address of array arr, so the value of ptr is the address of the first element of the array i.e &arr[0].

Since an array in C contains data items stored at contiguous memory locations, traversal of the array can be done by incrementing the pointer.

printf("%d", *ptr); //outputs first element of arr = 1
ptr++;
printf("%d", *ptr); //outputs second element of arr = 2
printf("%d", *(ptr+2) //ptr currently points to second element so +2 would make it point to 4th element i.e 4

There's also another kind of array pointer that points to the entire array instead of the first element. The initialization and declaration slightly vary from the previous implementation.

int arr[5] = {1, 2, 3, 4, 5};
int (*ptr2)[5]; //pointer to array of 5 integers
ptr2 = &arr //ptr points to the whole array
ptr2++;
printf("%d", ptr2) //outputs address 0x7fff4f32fd64

While ptr is a pointer that points to the first element in the array, ptr2 is a pointer that points to the whole array arr. The type of ptr is int while the type of ptr2 is an array of 5 integers. ptr++ shifts the pointer by 4 bytes (size of int), ptr2++ shifts the pointer by 20 bytes (4*5). Note that 4 bytes is the size of integer and 5 is the length of the array. If the base address of an array is 3000 then ptr2++ would end up pointing to address 3020.

Call by value vs Call by reference

data can be passed into the function in two ways -

  1. call by value

  2. call by reference

Actual parameters are those parameters that are passed to the function while parameters that are received by the function are called formal parameters.

In call by value, a copy of the variable is passed to the function. So the formal and actual parameters exist as separate entities in different locations. Changes made to one will not affect the other.

int main()
{
    int arr[5]={1, 2, 3, 4, 5};
    int sum = add(arr, 5); //pass copy of arr and length of array (5) to function add
}
int add(int array[], int len) //function to return sum of elements in array
{
    int sum = 0;
    for(int i=0; i<len; i++)
    {
        sum += array[i];
    }
    return sum;
}

In call by reference, the address of the variable is passed to the function. So the formal and actual parameters are the same as they refer to the same location. Changes made to one will affect the other. In the example below we pass the array arr as a parameter. The formal pointer is taken as the address pointing to the first index of arr (denoted by array). Name of the array is a constant pointer that points to the 0th index of the array i.e *(arr)==arr[0]

int main()
{
    int arr[5]={1, 2, 3, 4, 5};
    int sum = add(arr, 5); //pass address of arr and length of array (5) to function add
}
int add(int *array, int len) //function to return sum of elements in array
{
    int sum = 0;
    for(int i=0; i<len; i++)
    {
        sum += *(array+i); //*(array+i) is the same as array[i]
    }
    return sum;
}

Multidimensional array pointers

Each cell in a multidimensional array can be accessed by using its unique row number and column number. Since it was established from call by reference that name pointer, points to first index of the array, accessing array element arr[i][j] (i = row number , j = column number) is performed by (*(arr+i)+j).

In computer memory, 2D arrays are stored as a linear array of rows placed next to each other. In the above image, arr is an array of 3 elements where each element is a 1-D array of 4 integers. So *arr points to the first row, arr+1 to the second row and so on. Elements iniside the particular row can be accessed by (*(arr+i)+j) [i = row number , j = column number]. (\(arr+2)+1)* points to element at arr[2][1] i.e 10.

int arr[3][4] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} };
printf("%d", (*(arr + 1) + 2)); //same as arr[1][2] == 7

Dynamic memory allocation

One of the most important use cases of C is in dynamic allocation of memory. Memory that is allocated at runtime instead of during compile time is called dynamic memory allocation. This is achieved using 4 functions defined in the stdlib.h library - malloc(), calloc(), realloc() and free().

  1. malloc(size_t size) : Allocates a block of size bytes of memory and returns a pointer to the first byte of the block.

  2. calloc(size_t num, size_t size) : Allocates a block of memory for an array of num elements, each of size bytes, and returns a pointer to the first byte of the block. calloc initializes the memory block to zero, while malloc does not.

  3. realloc(void \ptr, size_t size)* : used to increase or decrease the size of the memory block pointed by ptr.

  4. free(void \ptr)* : Frees the memory block pointed to by ptr.

int n, i;
int *ptr;
printf("Enter the number of elements: ");
scanf("%d", &n);
ptr = (int*)malloc(n * sizeof(int)); // Allocate memory using malloc
//if n==5 then the above statement allocates 4*5 = 20 bytes of memory
if (ptr == NULL)
{
    printf("Memory allocation failed.\n"); 
    exit(1);
}                 
printf("Enter elements: ");
for (i = 0; i < n; i++)
{
    scanf("%d", ptr + i);
    printf("%d th element is %d ", i, *(ptr + i)); //1st element is number        pointed by (ptr+i)
}    
printf("Elements are: ");    
for (i = 0; i < n; i++)
{

}  
free(ptr); // Free memory block allocated to prevent memory leakage

Double Pointers

Double pointers are also called pointer to pointer. The first pointer stores the address of the second pointer.

int var = 76;
int *p1, 
int **p2; //double pointer
p1 = &var;
p2 = &p1; //p2 points to address of pointer p1
printf("%d\n", *p1 ); //outputs value of var = 76
printf("%d\n", **p2); //outputs value of var = 76

Similarly, there also exist triple pointer which are represented as name of the pointer preceded by three asterisks [\**ptr*].

Data Structures with Pointers

Pointers are indispensable in creation of data structures like trees , linked lists, stacks, queues and graphs.

  1. Linked List : a linear data structure where each element (node) is a separate object that contains a reference to the next element.

  2. Tree : a non-linear data structure that consists of nodes and edges. References to nodes are maintained with pointers.

  3. Stack : a linear data structure that follows the Last In First Out (LIFO) principle. Implemented using pointers to store the reference to the topmost element of the stack.

  4. Queue : a linear data structure that follows the First In First Out (FIFO) principle. Uses two pointers to maintain front and rear elements of queue.

  5. Graph : a non-linear data structure that consists of nodes (vertices) and edges. Pointers are used to maintain references to neighboring nodes.

Some resources on pointers

Pointers are a fundamental concept in the C programming language and are often a topic of discussion in programming interviews. Some useful online resources that i find most helpful are mentioned below -

By mastering the use of pointers, you can write more efficient, flexible, and scalable programs in C. Whether you're just starting out or have been programming for years, it's essential to have a solid understanding of pointers and how they work. With practice and patience, you can gain confidence in using pointers and take your C programming skills to the next level.

In short, pointers are a valuable tool in the arsenal of a C programmer, and their mastery is an important step towards becoming a proficient C developer.