Pointers in C — Complete Lesson
0%
C Programming  ·  Pointers

Pointers in C

The most important and most feared topic in C — explained simply. What a pointer is, how to declare one, how & and * work, pointer arithmetic, and how arrays and pointers connect.

What is a pointer
& and * operators
Memory diagram
Pointer arithmetic
Pointers & arrays
Pointers & functions
Null pointer
§1

What is a Pointer? — The Address Idea

Core concept

Every variable in your program is stored in computer memory. Every memory location has an address — like a house number on a street. A pointer is a variable that stores an address instead of a value.

Normal variable: stores a value like 42 or 3.14.
Pointer variable: stores an address like 0x7fff...a4 — the location of some other variable.

Simple analogy: Your friend's house number written in your diary. The diary entry is the pointer. Your friend's house is the variable. The house number written down is the address.

Memory picture — variable x stored at address 1000, pointer p stores that address
addr 1000
42int x
addr 2000
1000int *p (stores address of x)
p = 1000 (address of x)  ·  *p = 42 (value at that address)
Why do we need pointers? Three big reasons: (1) Functions can modify the caller's variables — pass the address instead of a copy. (2) Arrays can be passed efficiently — no copying. (3) Dynamic memory allocation — malloc() returns a pointer. Pointers are everywhere in C once you look.
declaring pointers
§2

Declaring a Pointer — The * Symbol

Syntax

You declare a pointer by putting a * before the variable name. The data type before the * tells C what type of variable this pointer will point to.

int *p means "p is a pointer to an integer". float *q means "q is a pointer to a float".

Declaration syntax
data_type  *pointer_name;

int    *p;      /* pointer to int */
float  *q;      /* pointer to float */
char   *c;      /* pointer to char */
double *d;      /* pointer to double */

Two operators work with pointers every time:

  • & (address-of operator) — placed before a variable, gives its address. &x means "the address of x". This is how you make a pointer point at something.
  • * (dereference operator) — placed before a pointer, gives the value at that address. *p means "go to the address stored in p and read the value there".
pointer_basics.c
C
#include <stdio.h>

int main() {
    int  x = 42;     /* normal variable */
    int *p;          /* pointer to int — NOT yet pointing anywhere */

    p = &x;          /* p now holds the ADDRESS of x */

    printf("Value of x      = %d\n",   x);
    printf("Address of x    = %p\n",   &x);  /* address */
    printf("Value of p      = %p\n",   p);   /* p holds address of x */
    printf("Value at *p     = %d\n",  *p);   /* dereference: value at address */

    /* Changing x through the pointer */
    *p = 100;        /* goes to the address p holds, writes 100 there */
    printf("\nAfter *p = 100:\n");
    printf("x is now = %d\n", x);   /* x changed! */

    return 0;
}
output
Value of x      = 42
Address of x    = 0x7fff5a4c (example)
Value of p      = 0x7fff5a4c (same address)
Value at *p     = 42

After *p = 100:
x is now = 100
The key insight — p and x are linked: p = &x means p and x are connected. *p IS x. Changing *p changes x because they are the same memory location. p just holds the address that leads to x.
& and * clearly
§3

& and * — Two Opposite Operations

Most confusing part

& and * are exact opposites:

  • &x — start with variable x, give me its address
  • *p — start with address p, give me the value stored there

So *(&x) = x. You take the address of x and immediately dereference it — you get x back.

ExpressionRead asWhat you getExample result
xthe variable xThe value stored in x42
&xaddress of xThe memory address of x0x7fff5a4c
pthe pointer pWhatever address p stores0x7fff5a4c
*pvalue at address pThe value stored at p's address42
&paddress of p itselfMemory address where p lives0x7fff9b00
*(&x)deref address of xSame as x — cancel out42
ampersand_star.c
C
#include <stdio.h>

int main() {
    int  a = 10, b = 20;
    int *p = &a;  /* p points to a */
    int *q = &b;  /* q points to b */

    printf("a = %d,  b = %d\n", a, b);
    printf("*p = %d, *q = %d\n", *p, *q);

    /* p now points to b instead */
    p = &b;
    printf("After p = &b:  *p = %d\n", *p); /* 20 */

    /* Change b through p */
    *p = 99;
    printf("After *p=99:   b = %d\n", b);  /* b changed to 99! */

    return 0;
}
output
a = 10,  b = 20
*p = 10, *q = 20
After p = &b:  *p = 20
After *p=99:   b = 99
Never dereference an uninitialised pointer! int *p; *p = 5; — p doesn't point anywhere yet. Reading or writing through it causes a crash (segmentation fault). Always assign a valid address before using *p: either p = &someVariable or p = malloc(...).
pointer arithmetic
§4

Pointer Arithmetic — Moving Through Memory

Arithmetic

You can add or subtract integers to/from a pointer. When you write p + 1, it doesn't add 1 to the address — it adds sizeof(type) bytes. So for an int *p, p + 1 moves forward by 4 bytes (the size of int). This makes walking through arrays natural and fast.

int arr[4] = {10, 20, 30, 40} — p = &arr[0] — pointer arithmetic
p → 1000
10arr[0] *p
20arr[1] *(p+1)
30arr[2] *(p+2)
40arr[3] *(p+3)
p+1 = 1004 (not 1001) · each int = 4 bytes · p+2 = 1008 · p+3 = 1012
pointer_arithmetic.c
C
#include <stdio.h>

int main() {
    int  arr[4] = {10, 20, 30, 40};
    int *p = arr;   /* p = &arr[0] — same thing */
    int  i;

    printf("Using pointer arithmetic:\n");
    for (i = 0; i < 4; i++) {
        printf("*(p+%d) = %d\n", i, *(p + i));
    }

    printf("\nUsing p++ to walk:\n");
    p = arr;   /* reset to start */
    for (i = 0; i < 4; i++) {
        printf("%d ", *p);
        p++;   /* move to next element */
    }
    printf("\n");

    return 0;
}
output
Using pointer arithmetic:
*(p+0) = 10
*(p+1) = 20
*(p+2) = 30
*(p+3) = 40

Using p++ to walk:
10 20 30 40
OperationMeaningFor int* (4 bytes)
p + 1Next elementAddress + 4 bytes
p - 1Previous elementAddress − 4 bytes
p++Move to next, return oldAddress += 4
++pMove to next, return newAddress += 4
p2 - p1Number of elements between(addr2 − addr1) / 4
p1 == p2Do they point to same location?Compare addresses
pointers and arrays
§5

Pointers and Arrays — They Are Connected

Key relationship

In C, an array name is a pointer to the first element. arr and &arr[0] are the same thing — the address of the first element. This means arr[i] and *(arr + i) are identical — the compiler converts one into the other automatically.

array_pointer_link.c
C
#include <stdio.h>

int main() {
    int arr[5] = {11, 22, 33, 44, 55};
    int i;

    printf("arr == &arr[0]: %s\n",
           arr == &arr[0] ? "YES" : "NO");

    printf("\nArray notation vs pointer notation:\n");
    printf("%-15s %-15s\n", "arr[i]", "*(arr+i)");
    printf("------------------------------\n");
    for (i = 0; i < 5; i++)
        printf("arr[%d] = %-8d *(arr+%d) = %d\n",
               i, arr[i], i, *(arr + i));

    return 0;
}
output
arr == &arr[0]: YES

Array notation vs pointer notation:
arr[i]          *(arr+i)
------------------------------
arr[0] = 11       *(arr+0) = 11
arr[1] = 22       *(arr+1) = 22
arr[2] = 33       *(arr+2) = 33
arr[3] = 44       *(arr+3) = 44
arr[4] = 55       *(arr+4) = 55
arr[i] and *(arr+i) are 100% identical. The compiler converts arr[i] into *(arr+i) internally. This is why arrays in C are so fast — element access is just a simple address calculation.
pointers and functions
§6

Pointers and Functions — Pass by Reference

Most useful feature

Normally C passes a copy of a variable to a function — changing the copy doesn't affect the original. When you pass a pointer (the address), the function can reach back and modify the original variable directly. This is called pass by reference.

pass_by_reference.c
C
#include <stdio.h>

/* Correct swap — uses pointers to modify originals */
void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

/* Get min and max in one call — two output pointers */
void minMax(int arr[], int n, int *mn, int *mx) {
    *mn = *mx = arr[0];
    for (int i = 1; i < n; i++) {
        if (arr[i] < *mn) *mn = arr[i];
        if (arr[i] > *mx) *mx = arr[i];
    }
}

int main() {
    int x = 5, y = 9;
    printf("Before: x=%d y=%d\n", x, y);
    swap(&x, &y);          /* pass addresses */
    printf("After:  x=%d y=%d\n", x, y);

    int arr[] = {34, 7, 89, 12, 56};
    int mn, mx;
    minMax(arr, 5, &mn, &mx);
    printf("Min=%d  Max=%d\n", mn, mx);
    return 0;
}
output
Before: x=5 y=9
After:  x=9 y=5
Min=7  Max=89
minMax returns two values through pointers. C functions can only return one value — but by passing two output pointers you can fill in two results. scanf uses this exact technique — it takes &variable to write the typed value directly into your variable.
null pointer
§7

NULL Pointer — A Safe Empty Pointer

Safety

A NULL pointer is a pointer that points to nothing. It is defined as 0 (or NULL from stdio.h). Use it when you want to say "this pointer is not currently pointing anywhere". You can check if (p == NULL) before using it — this prevents crashes from dereferencing an invalid pointer.

null_pointer.c
C
#include <stdio.h>
#include <stdlib.h>

int main() {
    int *p = NULL;   /* safe empty pointer */

    /* Always check before using */
    if (p == NULL) {
        printf("p is NULL — not pointing anywhere\n");
    }

    /* Point it at something */
    int x = 55;
    p = &x;

    if (p != NULL) {
        printf("p now points to: %d\n", *p);
    }

    /* strstr returns NULL if not found */
    char *result = strstr("Hello World", "World");
    if (result != NULL)
        printf("Found: %s\n", result);

    return 0;
}
output
p is NULL — not pointing anywhere
p now points to: 55
Found: World
Dereferencing NULL crashes your program. *p when p == NULL is a segmentation fault. Always initialise pointers to NULL when you declare them, and check before use. Many library functions return NULL on failure — always check.
§8

Pointer to Pointer — Double Pointer

Advanced

A pointer stores an address. But a pointer is itself a variable — it has its own address. You can have a pointer that stores the address of another pointer. This is called a double pointer and is declared with **.

double_pointer.c
C
#include <stdio.h>

int main() {
    int   x = 42;
    int  *p  = &x;   /* p  holds address of x */
    int **pp = &p;   /* pp holds address of p */

    printf("x   = %d\n",    x);
    printf("*p  = %d\n",   *p);   /* value at p = x = 42 */
    printf("**pp= %d\n",  **pp);  /* value at *pp = *p = x = 42 */

    /* Modify x through double pointer */
    **pp = 999;
    printf("x after **pp=999: %d\n", x);

    return 0;
}
output
x   = 42
*p  = 42
**pp= 42
x after **pp=999: 999
quiz
Q

Quick Quiz

Q 1 of 5

What does int *p = &x do?

Q 2 of 5

If int arr[5], what does p + 1 give when int *p = arr?

Q 3 of 5

Are arr[i] and *(arr + i) the same thing?

Q 4 of 5

Why does swap(int *a, int *b) work but swap(int a, int b) does not?

Q 5 of 5

What happens if you dereference a NULL pointer: int *p = NULL; *p = 5;?

Lesson Checklist

  • A pointer stores a memory address, not a value
  • &x gives the address of x — used to make a pointer point to x
  • *p dereferences p — reads or writes the value at the address p holds
  • int *p declares a pointer to int — the type tells C how many bytes to read/write
  • Never use *p before assigning a valid address to p
  • Pointer arithmetic: p+1 moves by sizeof(type) bytes — not 1 byte
  • arr[i] and *(arr+i) are identical — array name is a pointer to first element
  • Pass &variable to a function to let it modify the original (pass by reference)
  • NULL pointer = safe empty pointer — always check != NULL before dereferencing
  • I completed the quiz