Reading: Pointers in C

In the C language, a pointer is a variable whose value is the address of another variable. We can think of a pointer as an intermediary, namely a variable that points to a final location or to another intermediary as shown in the image and code below.

Simple and double pointer

#include <stdio.h>

int main()
{
    int v;
    int *p;  /* pointer to a 32-bit integer */
    int **pp;  /* pointer to a pointer holding the address of a 32-bit integer */

    /* To access the address of a variable in C, we use the address-of operator '&' */
    p = &v;  /* p holds the address of value v */
    pp = &p;  /* pp holds the address of the address of value v */

    v = 69;
    /* To access the value at the address stored by a pointer, we use the dereference operator '*' */
    printf("v(%d) - *p(%d) - **pp(%d)\n", v, *p, *(*pp));  /* outputs v(69) - *p(69) - **pp(69) */

    return 0;
}

Advantages of Pointers

  • Pointers are used in creating complex data structures such as linked lists, trees, graphs, hash tables, etc.
  • Pointers are used to transfer information between different functions or recursive calls without using global variables.
  • By using pointers, we can dynamically allocate memory.
  • We can have other functions, strings, complex data structures as parameters for functions.

Disadvantages of Pointers

  • Using an uninitialized pointer in a program leads to a segmentation fault by accessing a restricted memory area.
  • Manual memory deallocation is required by the programmer for dynamically allocated memory.
  • Dereferencing is needed to access a value, which is slower than direct access.

In C, a pointer can be defined for any of the data types existing in the language as well as for void. A void pointer differs from a pointer to an explicit data type in that a void pointer CANNOT be used in pointer operations, as void does not have a clear size. A basic example where pointers and pointer operations are used is the allocation and traversal of an array of values:

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

#define ARR_LENGTH 5

int main()
{
   int *arr, i;

   arr = (int *)malloc(sizeof(int) * ARR_LENGTH);
   // arr = (int *)calloc(ARR_LENGTH, sizeof(int));

   for (i = 0; i < ARR_LENGTH; ++i) {
       /*
        * arr + i iterates through the addresses of each element in the array, but the address arr + i doesn't increase by i but by i * sizeof(int), as arr is a pointer to int
        * This operation is not visible or necessary in C but will be required later in assembly language
        */
       printf("arr[%d] = %d: ", i, *(arr + i));
   }

   free(arr);
   return 0;
}

Pointers offer great flexibility regarding memory access. Below is an example that checks if a system is little or big endian using casting between different types of pointers.

#include <stdio.h>

int main()
{
    int v = 0x00000001;
    unsigned char *first_byte = (unsigned char *)&v;

    if (*first_byte == 0x01)
        printf("little-endian\n");
    else
        printf("big-endian\n");

    return 0;
}