C Pointers

I would make a joke about pointers, but you would not get the reference.

In my last post, I've written about lots of things but not about C pointers. I thought the topic deserves a post in itself because it's one of the most important features of the C language.

Let's start with some music please.

C Pointers

ToC

Introduction:

A pointer is a variable that contains the memory address of another variable. A pointer allows us to indirectly access and manipulate the value of the variable that is pointed to. Like other variables, C pointers have a type.

Pointers

Source

Declaration and initialization:

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


int main () {
  int foo = 42;
  int bar = 83;
  int *ptr = &foo;  // Pointer variable, type int, receives the address of foo variable*
  int **ptrptr = &ptr; // pointer to a pointer

  printf("Value of foo: %d\n", foo);
  printf("Value of ptr is: %p, and ptr is pointing to: %d\n", (void *)ptr, *ptr);
  printf("Value of ptrptr is: %p & ptrptr is pointing to: %p\n", (void *)ptrptr, (void *)ptr);

  printf("\n");
  *ptr = 12;
  printf("Value of foo after modification via ptr: %d\n", foo);
  **ptrptr = 13;
  printf("Value of foo after modification via ptr to ptr: %d\n", foo);

  printf("\n");
  ptr = &bar;
  printf("Value of ptr is now: %p, and ptr is now pointing to: %d\n", (void *)ptr, *ptr);
  printf("Value of ptrptr, still: %p & ptrptr is now pointing to: %p\n", (void *)ptrptr, (void *)ptr);

  exit(EXIT_SUCCESS);
}

And the output of the previous program is:

Value of foo: 42
Value of ptr is: 0x7ffc6155fd60, and ptr is pointing to: 42
Value of ptrptr is: 0x7ffc6155fd68 & ptrptr is pointing to: 0x7ffc6155fd60

Value of foo after modification via ptr: 12
Value of foo after modification via ptr to ptr: 13

Value of ptr is now: 0x7ffc6155fd64, and ptr is now pointing to: 83
Value of ptrptr, still: 0x7ffc6155fd68 & ptrptr is now pointing to: 0x7ffc6155fd64

C pointer operators:

C offers operators for pointer manipulation:

  • Address-of operator (&): to return the memory address of a variable
  • Dereference operator (*): to access the value stored at the memory address pointed to
  • Arithmetic operators: to perform arithmetic operations on C pointers, addition (+), subtraction (-), increment (++), and decrement (--)

Use cases for C pointers:

All modern programming languages have pointers in some form or other. Pointers are used everywhere there is a program, even when we don't see them. Let's explore some of those use cases.

Manipulating array and string elements:

C Pointers can be used to access and manipulate elements in arrays and strings. C Pointers provide a way to efficiently iterate over arrays and perform operations on each item of the sequence.

int arr[5] = {1, 2, 3, 4, 5};
int *aptr = arr;

// print items of the sequence using array indices
for (int i = 0; i < 5; i++) {
  printf("%d ", arr[i]);
}
printf("\n");

// print elements of the array using pointer
for (int i = 0; i < 5; i++) {
  printf("%d ", *aptr);
  aptr++;
}

// print items of the sequence using pointer arithmetic
// and the fact that arr is actually a pointer to the first item of the arr
for (int i = 0; i < 5; i++) {
  printf("%d ", *arr+i);
}
// an array of characters
char str[] = "Hello world from West Africa";
char *sptr = str; // a pointer to the first element of the array

int i = 0;
while(i < 28){
  // while i is less than 28, print the character at i-th index 
  printf("%c ", str[i]);
  // increment i to go to the next index
  i++;
 }

printf("\n");

// while the value that is pointed to is different from the "\0" character
while (*sptr != '\0') {
  // print the char
  printf("%c ", *sptr);
  // increment pointer (to go to the next character)
  sptr = sptr + 1;
}

printf("\n");

int m = 0;
while(m < 28){
  // str is actually a pointer to the first item of the object
  // so str+m is the address of the m-ieth item of the object
  printf("%c ", *(str+m));
  m++;
}
// array of pointers to characters
char *names[] = {"Alice", "Bob", "Charlie", "David", "Emma"};
// Pointer to the first element of the first element of the array of strings
char **ptr_names = names; 

for (int j = 0; j < 5; j++) {
  // Print elements of the array of strings using indices
  printf("%s\n", names[j]);
}

for (int x = 0; x < 5; x++) {
  // Print the element currently pointed to
  printf("%s\n", *ptr_names);
  ptr_names = ptr_names + 1;
}

Passing arguments by reference:

C Pointers can be used to pass arguments by reference to functions, allowing the function to modify the value of the original variable. This is particularly useful in the following situations:

  • working with large data structures
  • you need to modify the value of a variable in a function
  • you need to optimize code by reducing the number of copies of data that need to be made
  • you need to return more than one value

Example:

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

// let's suppose this is a laaaaaarge struct
typedef struct {
  int id;
  char name[50];
  float salary;
} Employee;


void update_employee(Employee* emp) {
  emp->salary *= 1.1; // 10% raise
}


int main() {
  // We create the large structure once
  Employee e1 = {1, "John Doe", 5000};

  printf("Employee: %s, Salary: %.2f\n", e1.name, e1.salary);
  // We pass the large structure by reference
  // to avoid the overhead of copying the entire structure.
  update_employee(&e1);
  printf("Employee after update: %s, Salary: %.2f\n", e1.name, e1.salary);

  exit(EXIT_SUCCESS);
}

Allocating memory:

C Pointers are also used to allocate memory dynamically at run-time using functions like malloc(), calloc(), and realloc(). Examples:

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

// a huuuuuuuge structure
typedef struct {
  int id;
  char name[50];
  float salary;
} Employee;

// thanks to pointer & to malloc,
// we can create the structure inside the create_employee function
// and use the structure even after the function goes out of scope
Employee* create_employee(){
  // Allocate memory space on heap, size should be the same as width of Employee struct
  // Allocate a pointer on stack,
  Employee* e2 = malloc(sizeof(Employee));
  // always verify the allocation worked fine
  if (e2 == nullptr) {
    printf("Memory allocation failed!\n");
    exit(EXIT_FAILURE);
  }
  // the ptr e2 contains the value of the memory address of the struct that is on the heap  
  return e2;
}

void init_employee(Employee* emp, int id, const char* name, float salary) {
  emp->id = id;
  strcpy(emp->name, name);
  emp->salary = salary;
}

void update_employee(Employee* emp) {
  emp->salary *= 1.1; // 10% raise
}


int main() {
  Employee* e2 = create_employee();
  init_employee(e2, 2, "Nsukami Patrick", 6000);

  printf("Employee: %s, Salary: %.2f\n", e2->name, e2->salary);
  update_employee(e2);
  printf("Employee after update: %s, Salary: %.2f\n", e2->name, e2->salary);

  // never forget to free the allocated memory
  free(e2);
  exit(EXIT_SUCCESS);
}

Implementing data structures:

Linked lists, Stacks, Queues, Trees, Graphs and many other data structures are implemented using C Pointers. Example:

Calling functions:

A function pointer, is a pointer which point to the memory address of a function instead of pointing to the address of a data object. They are used to:

  • store functions in data structures
  • pass functions as arguments to other functions
  • call functions dynamically at runtime

Here are some examples on how to use function pointers in C.

Interfacing with hardware:

C Pointers are often used in low-level programming to interact with hardware devices and memory-mapped registers. They provide a way to access specific memory addresses and manipulate hardware directly. This part goes way beyond what my brain is able to understand. Hopefully, I'll be able to show you an example in another article.

Going further:

To learn more about C pointers, we recommend the following pointers:


Pointers