Introduction to Pointers

CSL 102; Data Structures; IIIT Nagpur; Created by: Dr. Aniket Pingley


Why you must learn and master pointers

Many popular languages today do not use pointers, e.g., Python, Java, CSharp, Javascript etc. It is obvious that one might wonder why educators focus on teaching pointers and expect the students to master the subject.

Generally speaking, coding or programming in any language is essentially a practice or skill to implement a piece of logic to manage and process data. From a coder's perspective this data is available in the main memory (RAM) of a computer system. While in elementary examples of coding, singular data is often used, complex scenarios require handling a high volume of data. It is thus important to understand how data can be structued and organized in the main memory, where certain ways of oragnizing the data makes its access and manipulation highly efficient in certain scenarios.

Since data is located in main memory, it is important to master the skills needed for its access. Pointers are instrument to work directly with the main memory in languages such as C and C++. Working with pointers enhances a coder's view of designing complex logic of accessing data.

A lot of commercial coding logic is implemented in C and C++ due its speed and light memory footprint. And, this commercial code uses pointers to manage different data structuring logic. It is thus important that students master the use of pointers, which will underpin their understanding of preliminary as well as advanced data structures.


Understanding the structure of main memory

Image source - https://faculty.etsu.edu/tarnoff/ntes2150/memory/memory.htm

Assuming that each cell is a D flip flop that contains a single bit, the first row (top) contains 8 bits. As an example, 'char' data type is represented using 8 bits (1 byte). For both 64-bit as well as 32-bit architectures, the main memory is byte-addressed. Thus, the top row can store value of a'char' type variable and accessible using a single byte address.

For data items larger than 'char', for example 'int', which can be either 16 or 32 bits, multiple rows of flip-flops will be used in order. A block of memory, notwithstanding the size of the variable-type, is addressed the first byte in the block. For example, an 'int' stored in bytes 2048-2049 (16 bits, 0x800-0x801 in hexadecimal) has address 2048. Programming languages that allow pointers (e.g., C and C++) implement the required logic to process a block of memory in accordance with the data type.

Note: The description above is a simplistic explanation. You will learn addressing scheme in much more detail in the Computer Architecture/Organization course.


What are pointers?

To answer that question, we must first understand what is an address of a variable. As mentioned above, any kind of data (char, int, float etc.) is stored in the main memory that is byte-addressed. An address is a numeric value that is typically expressed in hexadecimal format, which locates the value of given variable in the main memory. In 'C' code the ampersand (&) sign is appended before a variable to extract its address.

A pointer is block of memory that can hold an address of a variable. The size of this block of memory is either 32 bits (4 bytes) or 64 bits (8 bytes) depending on the architecture of the system. In 'C' code, a pointer is represented using a variable with a special asterisk symbol '*'. The unary operator asterisk is called indirection of dereferencing operator. For example:

int var_a = 9;
int *ptr_a = &var_a;
/* writing int* ptr_a is equivalent to int *ptr_a (placement of the asterisk) */

The variable 'ptr_a' holds the address at which the value assigned to 'var_a'(9, in this case) in located in the main memory. It must be noted that we must declare a pointer variable to be of the same type as the data type of the variable whose address is pointed.

The address of a variable and the value of the variable can be printed using pointers in the following way:

ptr_a will output the address (hexadecimal) while *ptr_a will output the value of var_a

_

printf("address of var_a = %p, value of var_a = %d\n",ptr_a, *ptr_a); 

Here is a pictographic notation for ease of understanding.


Null pointer

If a pointer holds the value 0, the it is called null pointer. Typically, a pointer will hold the value 0 at the initialization, which is a good coding practice. If your code tried to access the value pointed by a null pointer, it will result into crashing of your program due to segmentation fault. For example:

char* p = NULL; // or char* p = 0;

Code snippet for pratice

#include <stdio.h>

int main(){
    int a = 5, b = 0;
    printf("Value of a = %d.\n Address of a = %p.\n", a, &a);

    int* ptr = NULL; /* initialize a ptr to NULL */
    ptr = &a; /* variable ptr now contains address of a */
    printf("Value of ptr = %p.\n Value at address pointed by p = %d.\n", ptr, *ptr);

    b = *ptr; /* the value of b is now overwritten with value of a*/
              /* this is equivalent to writing b = a; */
    printf("Value of b = %d.\n", b);

    return 0;
}

Pointer to a pointer a.k.a double-pointer

First, the use case for having the feature for pointing to a pointer may be not intuitive for beginners. Let's leave the use cases for future. At this moment, it will suffice to understand that variables that are pointers to a given data type are located at some address in main memory. A special sytanx if needed in 'C' language to declare variables that can point to the address at which a pointer is located. Instead of a single asterisk, double asterisk is used to declare a variable that is a pointer to a pointer. You will see more in the example below.

Image source - https://media.geeksforgeeks.org/

For those who are unclear - it is perhaps the jargon that makes it unintuitive. The best way to understand is to put it in 'C' code and play around with it to observe how changing a bit of code could result in different output. Sometimes our brain trains itself better with reverse engineering.

#include <stdio.h>

int main(){
    int a = 111;
    printf("Value of a = %d.\n Address of a = %p.\n", a, &a);

    int* ptr = NULL; /* initialize a ptr to NULL */
    ptr = &a; /* variable ptr now contains address of a */
    printf("Value of ptr = %p.\n Value at address pointed by p = %d.\n", ptr, *ptr);

    /* Working with Pointer to Pointers*/
    int **dblPtr = NULL; /* double pointer */
    dblPtr = &ptr; /* dblPtr points to the address where ptr is located.
    '*dblPtr' will point to the address pointed by ptr, i.e. address of a */
    if(ptr == *dblPtr){
        printf("ptr == *dblPtr is true.\n");
        printf("Address of 'dblPtr' = %p. \n", &dblPtr);
        printf("Address of 'ptr' and value in dblPtr = %p. \n", dblPtr);
        printf("Address of 'a' = %p. \n", *dblPtr);
        printf("Value at address pointed by 'ptr' = %d. \n", **dblPtr);
    }
    return 0;
}

Pointer arithmetic

Just like arithmetic operations on int and float data type, even pointers can be incremented, decremented, added with another pointer or subtracted from a pointer. After all, pointers are also numbers that are typically expressed in hexadecimal format. Here is a list of arithmetic operations for pointers:

When a pointer is incremented, it increments by the number equal to the size of the respective data type. For example, if an integer pointer (type 'short') stores address 1024 then the '++' operator will increment by 2 (bytes). The new value of pointer will now have address 1026.

int main(){
    int a = 111;

    int* ptr = NULL; /* initialize a ptr to NULL */
    ptr = &a; /* variable ptr now contains address of a */
    printf("Value of ptr = %p.\n", ptr);
    ptr++; /*increment the value of pointer.*/
    printf("Value of ptr after increment = %p.\n", ptr);
    ptr--;
    printf("Value of ptr after decrement = %p.\n", ptr);
    
    int *ptr2 = ptr;
    ptr2++;
    int diffVal = ptr2 - ptr; 
    /*This operation above show the size of integer in bytes*/
    /*However we are operating on two hexadecimal numbers. To get the correct value in
    bytes, first the hexadecimal value must be typecast/converted to integer format. */
    diffVal = (unsigned long int)ptr2- (unsigned long int)ptr;
    printf("Size of integer in bytes = %d.\n", diffVal); 

    int *ptr3 = ptr + 2; 
    /*ptr + 2 will add twice the number of bytes as per 
    the byte-size of int data type i.e. 4 bytes*/  
    diffVal = (unsigned long int)ptr3- (unsigned long int)ptr;
    printf("Size of integer in bytes = %d.\n", diffVal); 

    /*Pointer can be used to manipulate the value of variable it points*/
    ++*ptr; /*++ptr is different from ++*pt*/
    printf("New value of 'a' pointed by 'ptr' = %d.\n", *ptr);

    return 0;
}

Pointers and functions

A function can take address of variable(s) as input. Once the address of a variable become available to a called function that was passed from another (callee) function, the value of that variable can be manipulated inside the called function. Below is a C example to demonstrate.

void swap(int *ptr_x, int *ptr_y)
{
    int temp = *ptr_x;
    *ptr_x = *ptr_y;
    *ptr_y = temp;
}
  
int main()
{
    int x = 10; int y = 20;
    /*The addresses of variable x and y are passed as arguments to the swap function*/
    swap(&x, &y);
    printf("Swapped values are: x = %d, y = %d\n", x, y);
    return 0;
}



HOME   PREV   NEXT