Educational Blog

How to Use Pointers in C

Learn pointers in C with clear examples, memory basics, and common mistakes to avoid.

Pointers are one of the first C topics that feels mysterious and then suddenly practical. A pointer is just a variable that stores an address, but that small idea unlocks dynamic memory, arrays, string handling, function parameter updates, and data structures such as linked lists and trees. If you want to use C effectively, you need to be comfortable reading pointer syntax and predicting what lives at each memory location.

This guide walks through the core pointer ideas in plain language, shows the common operators, and then builds toward the patterns you actually use in real code. The goal is not just to memorize * and &, but to understand why they matter.

What a pointer is

A normal variable stores a value. A pointer stores the address of a value.

If x is an int, then &x means ?the address of x.? A pointer to int can hold that address:

int x = 42;
int *p = &x;

Here:

  • x holds the number 42
  • &x is where x lives in memory
  • p stores that address
  • *p means ?the value at the address stored in p?

That last part is called dereferencing.

The two operators you must know

OperatorMeaningExample
&address-of&x gives the address of x
*dereference or indirection*p gives the value pointed to by p

The same * symbol also appears in declarations, where it means ?this variable is a pointer.? That is why pointer syntax feels confusing at first. In int *p, the * is part of the type. In *p = 10, the * means dereference.

A first complete example

int main(void) {
    int x = 42;
    int *p = &x;

    printf("x = %d
", x);
    printf("&x = %p
", (void *)&x);
    printf("p = %p
", (void *)p);
    printf("*p = %d
", *p);

    *p = 99;
    printf("x after change = %d
", x);

    return 0;
}

This example shows the key rule: when p points to x, writing through *p changes x itself.

Why pointers are useful

Pointers solve problems that plain values cannot solve cleanly.

1. Modify data in another function

C passes arguments by value. If you want a function to update a caller?s variable, you pass its address.

#include <stdio.h>

void set_to_zero(int *n) {
    *n = 0;
}

int main(void) {
    int value = 17;
    set_to_zero(&value);
    printf("%d
", value);
}

Without the pointer, set_to_zero would only change a local copy.

2. Work with dynamic memory

Memory can be allocated at runtime using malloc and released with free.

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

int main(void) {
    int *arr = malloc(5 * sizeof(int));
    if (arr == NULL) {
        return 1;
    }

    for (int i = 0; i < 5; i++) {
        arr[i] = i * 10;
    }

    for (int i = 0; i < 5; i++) {
        printf("%d ", arr[i]);
    }
    printf("
");

    free(arr);
    return 0;
}

3. Build data structures

Linked lists, stacks, queues, graphs, and trees rely on pointers because each node needs to refer to other nodes.

Pointer basics in practice

Once you understand the address model, the syntax starts making sense.

Declaring pointers

int *ip;
char *cp;
double *dp;

Each pointer type should match the type of data it points to. A typed pointer lets the compiler know how many bytes to read when you dereference it and how far to move when you do pointer arithmetic.

Initializing pointers

Do not leave pointers uninitialized unless you intentionally want them to be null later.

int x = 5;
int *p = &x;
int *q = NULL;

NULL is a special pointer value that means ?points to nothing valid.? Before dereferencing a pointer, check that it is not NULL.

Dereferencing

int x = 10;
int *p = &x;

printf("%d
", *p);  // prints 10
*p = 25;
printf("%d
", x);   // prints 25

If p does not point to valid memory, dereferencing it causes undefined behavior.

Arrays and pointers

Arrays and pointers are closely related in C, but they are not identical.

An array name often decays to a pointer to its first element in expressions:

int nums[] = {10, 20, 30};
int *p = nums;

Here p points to nums[0].

You can access elements with either array syntax or pointer arithmetic:

printf("%d
", nums[1]);
printf("%d
", *(p + 1));

Important difference:

  • Arrays have fixed storage allocated for their elements
  • Pointers can be reassigned to point somewhere else

That distinction matters when you pass arrays to functions and when you manage heap memory.

Pointer arithmetic

Pointer arithmetic moves by the size of the pointed-to type.

int nums[] = {10, 20, 30};
int *p = nums;

printf("%d
", *p);       // 10
printf("%d
", *(p + 1)); // 20
printf("%d
", *(p + 2)); // 30

If p is an int *, then p + 1 advances by one int, not one byte. That is why you should avoid pretending pointers are raw integers. They are typed addresses.

Use pointer arithmetic carefully:

  • stay inside the same array object
  • never dereference past the end
  • do not compare unrelated pointers unless the rules make sense for the specific case

Passing pointers to functions

Passing pointers is how you let a function act on caller-owned data.

Example: swap two integers

#include <stdio.h>

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main(void) {
    int x = 3;
    int y = 7;

    swap(&x, &y);

    printf("x=%d y=%d
", x, y);
}

The function receives addresses, not copies of the values. That is what makes the exchange real.

Example: output parameters

Sometimes a function needs to return more than one result.

int divide(int a, int b, int *remainder) {
    *remainder = a % b;
    return a / b;
}

A pointer argument lets the function write an extra value back to the caller.

Common mistakes

Pointers are powerful, but the errors are often severe. These are the mistakes worth watching for every time.

  1. Dereferencing a null or uninitialized pointer
  2. Using memory after free
  3. Returning the address of a local variable
  4. Forgetting to free heap memory
  5. Mixing up *p and &p
  6. Treating unrelated pointer values as if they were interchangeable

Returning a bad pointer

int *bad(void) {
    int x = 5;
    return &x;
}

This is wrong because x disappears when the function returns. The address no longer refers to valid storage.

Using freed memory

int *p = malloc(sizeof(int));
*p = 12;
free(p);
/* p is now dangling */

After free(p), the pointer still contains a value, but the memory is no longer yours. Accessing it is unsafe.

A small pointer checklist

Before you use a pointer, run through this quick mental checklist:

  • Is it initialized?
  • Does it point to valid storage right now?
  • Is the type correct?
  • Is the lifetime of the memory long enough?
  • If it was allocated, will it be freed exactly once?

That checklist catches many bugs early.

Pointers and strings

In C, strings are usually stored as arrays of char terminated by . Because of that, strings are often handled through pointers.

char name[] = "Ada";
char *p = name;

You can walk through a string one character at a time:

while (*p != '�') {
    putchar(*p);
    p++;
}

This pattern appears everywhere in C code. Many library functions work by advancing a char * until they hit the terminator.

When to use pointers

Pointers are appropriate when you need:

  • direct access to existing data
  • mutation across function boundaries
  • dynamic allocation
  • traversal of arrays or buffers
  • linked structure references
  • interoperability with low-level APIs

You do not need pointers for everything. If a simple local value is enough, use one. Pointers are a tool for control and flexibility, not a default choice.

How to think about them

A helpful way to read pointer code is to separate three questions:

  1. What is the pointer variable storing?
  2. What object does that address refer to?
  3. Who owns that memory and for how long?

If you can answer those three questions, most pointer code becomes understandable. If you cannot, the code is probably unsafe or at least hard to maintain.

Practice ideas

If you want to get comfortable quickly, write and test these small exercises:

  • print the address and value of an int
  • modify a variable through a function argument
  • swap two numbers with pointers
  • allocate an array with malloc
  • iterate through a string with char *
  • build a single linked list node and follow its next pointer

These exercises force you to predict what lives in memory, which is exactly the skill pointer programming requires.

Quick reference

TaskPattern
Store addressint *p = &x;
Read pointed valuevalue = *p;
Write pointed value*p = 10;
Pass by referencefunc(&x);
Allocate memoryp = malloc(n * sizeof *p);
Release memoryfree(p);

Final takeaway

To use pointers in C, remember that the pointer itself is not the data. It is an address that lets you reach the data. Once you can trace addresses, dereference them safely, and respect object lifetime, pointers stop being magic syntax and start becoming one of the most useful parts of the language.

The fastest way to improve is to write tiny pointer programs, print addresses, and check your assumptions against the output. That habit turns pointer work from guesswork into a repeatable process.

Written by

c-double.com Editorial Team

Editorial team

c-double.com publishes practical how-to guides and educational articles with clear steps and useful context.