LearnFrenzy
Sign in Take a free test

Pointers · Lesson 22 of 35

Pointers in C Explained

5 min read Updated Nov 7, 2026 Saurabh Samir

Pointers are what make C powerful – and what scare most learners. The idea is simple: every variable lives at a memory address, and a pointer is a variable that stores an address. Through a pointer, a function can change the caller's variables, walk through arrays, and manage memory directly. Master this lesson and the rest of C opens up.

In this lesson

  • Addresses and the & operator
  • Declaring pointers and dereferencing with *
  • NULL and uninitialised pointers
  • Call by reference: functions that change your variables

Addresses

Memory is a long row of numbered bytes. When you declare int marks = 85;, the compiler reserves 4 bytes at some address – say 2000 – and stores 85 there. &marks ("address of marks") gives that address.

A variable and a pointer to it

  • marks85at address 2000
  • p2000an int pointer, at address 3000 – it "points to" marks
  • *p85the value at the address p holds

Declaring and using a pointer

#include <stdio.h>

int main(void) {
    int marks = 85;
    int *p = &marks;              // p is a "pointer to int" holding marks's address

    printf("marks       = %d\n", marks);
    printf("*p          = %d\n", *p);           // dereference: the value p points to
    printf("p == &marks ? %s\n", p == &marks ? "yes" : "no");

    *p = 92;                      // writing through the pointer changes marks
    printf("marks now   = %d\n", marks);

    int bonus = 5;
    p = &bonus;                   // a pointer can be re-pointed
    *p += 10;
    printf("bonus now   = %d, marks still %d\n", bonus, marks);
    printf("a pointer takes %zu bytes here\n", sizeof p);
    return 0;
}

Output

marks       = 85
*p          = 85
p == &marks ? yes
marks now   = 92
bonus now   = 15, marks still 92
a pointer takes 8 bytes here
SyntaxRead it as
int *p;p is a pointer to an int
p = &x;p gets the address of x
*pthe int that p points to (dereference)
*p = 7;store 7 in the int that p points to

The * means two different things: in a declaration (int *p) it says "p is a pointer"; in an expression (*p) it means "follow the pointer". To print an address, use printf("%p", (void *)p) – the value changes every time the program runs.

Declaring several pointers

int *a, b; makes a a pointer but b a plain int – the * belongs to each name. Write int *a, *b; or declare them on separate lines.

NULL and wild pointers

#include <stdio.h>

int find_index(const int a[], int n, int key) {
    for (int i = 0; i < n; i++) if (a[i] == key) return i;
    return -1;
}

int main(void) {
    int *p = NULL;                         // "points nowhere" – safe to test
    if (p == NULL) printf("p is not pointing at anything yet\n");

    int data[] = {4, 8, 15};
    int value = 8;
    int i = find_index(data, 3, value);
    p = (i >= 0) ? &data[i] : NULL;
    if (p != NULL) printf("found %d at index %d\n", *p, i);
    return 0;
}

Output

p is not pointing at anything yet
found 8 at index 1

Three dangerous pointers

  • Wild pointer: declared but never initialised (int *p; *p = 5;) – writes to a random address.
  • NULL dereference: *p when p is NULL – a guaranteed crash ("segmentation fault"). Check before using.
  • Dangling pointer: points to memory that no longer exists (a returned local, freed memory).

Initialise every pointer – to a real address or to NULL.

Call by reference

In chapter 5, swap_wrong couldn't change the caller's variables because it received copies. Pass addresses instead, and the function can reach the originals:

#include <stdio.h>

void swap(int *a, int *b) {
    int t = *a;      // value at address a
    *a = *b;
    *b = t;
}

void min_max(const int arr[], int n, int *min, int *max) {   // "return" two values
    *min = *max = arr[0];
    for (int i = 1; i < n; i++) {
        if (arr[i] < *min) *min = arr[i];
        if (arr[i] > *max) *max = arr[i];
    }
}

int main(void) {
    int x = 10, y = 20;
    swap(&x, &y);
    printf("x = %d, y = %d\n", x, y);

    int marks[] = {67, 92, 45, 78};
    int lo, hi;
    min_max(marks, 4, &lo, &hi);
    printf("min = %d, max = %d\n", lo, hi);
    return 0;
}

Output

x = 20, y = 10
min = 45, max = 92

swap(&x, &y) step by step

  1. a holds &x, b holds &ycopies of the addresses
  2. t = *at = 10 (the value of x)
  3. *a = *bx becomes 20
  4. *b = ty becomes 10

This is exactly how scanf("%d", &n) stores a value into n, and how functions return several results.

Practice

Try it yourself

  1. Write void divide(int a, int b, int *quotient, int *remainder) and use it on 47 and 5.

    Show solution
    #include <stdio.h>
    
    void divide(int a, int b, int *quotient, int *remainder) {
        *quotient = a / b;
        *remainder = a % b;
    }
    
    int main(void) {
        int q, r;
        divide(47, 5, &q, &r);
        printf("47 = 5 x %d + %d\n", q, r);
        return 0;
    }

    Output

    47 = 5 x 9 + 2

Quick check

If int x = 4; int *p = &x;, what is *p?

4.

What does *p = 9; do?

Stores 9 in the variable p points to (x becomes 9).

How can a function change two of the caller's variables?

Receive their addresses as pointer parameters and write through them.

Summary

  • A pointer stores an address; &x gives x's address and *p follows a pointer.
  • Initialise pointers to a valid address or NULL; never dereference NULL or dangling pointers.
  • Passing addresses (call by reference) lets functions modify the caller's variables.

Finished this lesson?

Mark it complete to track your progress, then carry on with the next lesson.

Next lesson

Search LearnFrenzy

Esc

Quick links