Learn C Language - Pointers & Memory Management
Episode 7 of 24

Learn C Language - Pointers & Memory Management

This episode dissects pointers, the heart of the C language: memory addresses, dereferencing, pointers to arrays, strings, and functions, dynamic memory allocation with malloc, calloc, realloc, and free, and pointer safety and common bugs like use-after-free and null pointers.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Pointers are the feature that makes C special and, at the same time, most dangerous. A pointer stores the memory address of a value, not the value itself. With pointers, you can manipulate large data without copying, build dynamic data structures, and call functions indirectly.

Episode 7 covers pointer basics and dereferencing, pointers to arrays, strings, and functions, dynamic memory allocation with malloc, calloc, realloc, and free, and safety patterns that prevent classic bugs like null pointer dereference and use-after-free.

Mastering pointers is the dividing line between someone who writes C code and someone who truly understands C. This episode is the most important investment in the whole series.

Pointer Basics and Dereferencing

Memory Addresses and the & Operator

Every variable has an address in memory. The & operator takes the address of a variable, and pointer types are declared with an asterisk after the base type:

Pointer basics
cat > pointer.c <<'EOF'
#include <stdio.h>
 
int main(void) {
    int nilai = 42;
    int *alamat = &nilai;
    printf("Nilai: %d\n", nilai);
    printf("Alamat: %p\n", (void *)alamat);
    printf("Dereference: %d\n", *alamat);
    return 0;
}
EOF
gcc -Wall -Wextra pointer.c -o pointer && ./pointer

int *alamat = &nilai stores the address of nilai in the pointer alamat. The * operator used in front of a pointer is called dereferencing — reading the value being pointed to. %p prints the address in hexadecimal form.

Pointers and Functions

Pointers let a function modify the original arguments, overcoming the passing by value limitation from episode 5:

Modify through a pointer
void tambah_satu(int *x) {
    (*x)++;
}
 
int main(void) {
    int angka = 10;
    tambah_satu(&angka);
    printf("%d\n", angka);
    return 0;
}

The function tambah_satu(&angka) receives the address of angka and changes its original value through dereferencing. Without a pointer, changes would only apply to the copy inside the function.

Pointers to Arrays, Strings, and Functions

The Array Name Is a Pointer

The name of an array acts as a pointer to its first element. This means nilai and &nilai[0] point to the same address. Pointer arithmetic enables element iteration:

Iterate an array with a pointer
int nilai[5] = {10, 20, 30, 40, 50};
for (int *p = nilai; p < nilai + 5; p++) {
    printf("%d ", *p);
}

The iteration for (int *p = nilai; ...; p++) advances the pointer one element per step, not one byte. The step size is determined by the pointer's type, so an int advances 4 bytes on common platforms.

Strings and Function Pointers

Immutable strings are stored as pointers to char: const char *s = "teks". Pointers to functions, as introduced in episode 5, store a function's address so functions can be chosen at runtime. Both show that pointers are a general concept, not specific to data.

Dynamic Memory Allocation

malloc, calloc, realloc, and free

Heap memory is allocated at runtime through four functions from <stdlib.h>:

  • malloc: allocates bytes without initializing them.
  • calloc: allocates and zero-fills.
  • realloc: resizes an existing block.
  • free: releases a block back to the system.
Dynamic allocation
cat > alokasi.c <<'EOF'
#include <stdio.h>
#include <stdlib.h>
 
int main(void) {
    int *data = malloc(5 * sizeof(int));
    if (data == NULL) {
        return 1;
    }
    for (int i = 0; i < 5; i++) {
        data[i] = i * i;
    }
    int *besar = realloc(data, 10 * sizeof(int));
    if (besar != NULL) {
        data = besar;
    }
    free(data);
    return 0;
}
EOF
gcc -Wall -Wextra alokasi.c -o alokasi && ./alokasi

The pattern malloc(5 * sizeof(int)) allocates enough memory for 5 ints. Always check for a NULL result, because allocation can fail. realloc returns a new pointer that may differ, and free must be called exactly once per block.

Measuring Leaks with Valgrind

A memory leak occurs when a block is allocated but never freed. Detect it with valgrind:

Detect memory leaks
valgrind --leak-check=full ./alokasi

The output of valgrind --leak-check=full ./alokasi reports blocks that were never freed. The sentence "All heap blocks were freed" means there are no leaks — the target you should always aim for.

Pointer Safety and Common Bugs

Null Pointers and Use-After-Free

The two most common memory bugs are:

  • Null pointer dereference: using a pointer whose value is NULL.
  • Use-after-free: using a pointer after the block it points to has been freed.
  • Double free: calling free twice on the same pointer.

All of the bugs above trigger undefined behavior that can crash or corrupt data. Preventive discipline: always initialize pointers, check for NULL, set pointers to NULL after free, and use a single owner for each memory block.

Safe free pattern
free(data);
data = NULL;

The sequence free(data); data = NULL; prevents use-after-free and double free because any use of a NULL pointer is immediately caught. This pattern is simple but saves production systems.

Warning

The behavior defined by the C standard is only a fraction of what seems reasonable. Dereferencing the wrong pointer, reading past an array, or using an already-freed block are all undefined behaviors that must be avoided from the start.

Closing

Key takeaways:

  • Pointers store memory addresses, not values; & takes an address and * dereferences.
  • An array name acts as a pointer to its first element.
  • malloc, calloc, realloc, and free manage heap memory manually.
  • Always check the NULL result of an allocation before using it.
  • Every malloc block must be freed exactly once.
  • Detect leaks with valgrind and avoid use-after-free.

In the next episode 8 we will discuss input/output and file handling — standard I/O with printf, scanf, getchar, and putchar, file I/O with fopen, fread, fwrite, fprintf, fgets, and fclose, file pointers and file modes, and I/O error handling with errno and perror.