Docs

Pointers

Introduction to Pointers in C

Table of Contents

  1. What are Pointers?
  2. Memory and Addresses
  3. Declaring Pointers
  4. The Address-of Operator (&)
  5. The Dereference Operator (*)
  6. Pointer Initialization
  7. NULL Pointers
  8. Pointer Arithmetic Basics
  9. Common Mistakes
  10. Why Use Pointers?
  11. Summary

What are Pointers?

A pointer is a variable that stores the memory address of another variable.

Regular Variable:              Pointer Variable:
┌─────────────────┐            ┌─────────────────┐
│  value: 42      │            │  value: 1000    │ ← Address!
│  address: 1000  │            │  address: 2000  │
└─────────────────┘            └─────────────────┘
       x                              ptr
                                       │
                                       └── Points to address 1000 (where x is)

Simple Analogy

Think of memory addresses like house addresses:

  • A variable is like a house that holds a value (the people living there)
  • A pointer is like a piece of paper with an address written on it
  • The pointer doesn't contain the value itself - just where to find it

Memory and Addresses

How Memory Works

Computer memory is organized as a sequence of bytes, each with a unique address.

Memory Layout:
Address:  1000   1001   1002   1003   1004   1005   1006   1007
        ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐
        │      │      │      │      │      │      │      │      │
        └──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘

int x = 42;  (int takes 4 bytes)
Address:  1000   1001   1002   1003   1004   1005   1006   1007
        ┌──────────────────────────┬──────────────────────────┐
        │    x = 42 (4 bytes)      │    (other data)          │
        └──────────────────────────┴──────────────────────────┘

Size of Types in Memory

TypeTypical SizeExample Address Range
char1 byte1000
short2 bytes1000-1001
int4 bytes1000-1003
long8 bytes1000-1007
float4 bytes1000-1003
double8 bytes1000-1007
pointer8 bytes (64-bit)1000-1007

Declaring Pointers

Syntax

type *pointer_name;

The * indicates this is a pointer to the specified type.

Examples

int *ptr;        // Pointer to int
char *cptr;      // Pointer to char
float *fptr;     // Pointer to float
double *dptr;    // Pointer to double

Style Variations

All of these declare an int pointer:

int *ptr;    // * with variable name (preferred)
int* ptr;    // * with type
int * ptr;   // * with spaces

Warning: Multiple Declarations

int *p1, *p2;    // TWO pointers
int *p1, p2;     // p1 is pointer, p2 is int (NOT pointer!)
int *p1, *p2;    // Always use * with each name

The Address-of Operator (&)

The & operator returns the memory address of a variable.

int x = 42;
printf("Value of x: %d\n", x);      // 42
printf("Address of x: %p\n", &x);   // 0x7ffd5e8c3abc (example)

Visual Representation

int x = 42;
int *ptr = &x;

Memory:
┌─────────────────────────────────────────┐
│ Address 1000-1003: x = 42               │
├─────────────────────────────────────────┤
│ Address 2000-2007: ptr = 1000           │
└─────────────────────────────────────────┘

&x returns 1000
ptr stores 1000

Using with scanf

int num;
scanf("%d", &num);   // Pass address of num
                     // scanf needs to know WHERE to store the input

The Dereference Operator (*)

The * operator (when used with a pointer) accesses the value at the address the pointer holds.

int x = 42;
int *ptr = &x;

printf("Address stored in ptr: %p\n", ptr);    // Address of x
printf("Value at that address: %d\n", *ptr);   // 42 (value of x)

Reading and Writing Through Pointers

int x = 42;
int *ptr = &x;

// Reading
int value = *ptr;    // value = 42

// Writing
*ptr = 100;          // x is now 100!
printf("x = %d\n", x);  // Output: 100

Visual Example

int x = 42;
int *ptr = &x;
*ptr = 100;

Step 1: Declaration
┌───────────┐     ┌───────────┐
│  x = 42   │     │ ptr = &x  │
│ addr:1000 │ ←── │ (1000)    │
└───────────┘     └───────────┘

Step 2: *ptr = 100 (write through pointer)
┌───────────┐     ┌───────────┐
│  x = 100  │ ←── │ ptr = &x  │  *ptr means "go to address
│ addr:1000 │     │ (1000)    │   1000 and change value"
└───────────┘     └───────────┘

Pointer Initialization

Always Initialize Pointers

// GOOD - initialized to valid address
int x = 10;
int *ptr = &x;

// GOOD - initialized to NULL
int *ptr = NULL;

// BAD - uninitialized (dangerous!)
int *ptr;  // Contains garbage address
*ptr = 5;  // CRASH! Accessing random memory

Different Ways to Initialize

int x = 42;

int *ptr1 = &x;       // Initialize with address
int *ptr2 = NULL;     // Initialize to null
int *ptr3 = ptr1;     // Initialize with another pointer

NULL Pointers

NULL is a special value indicating "points to nothing."

#include <stdio.h>
#include <stdlib.h>  // or <stddef.h> for NULL

int *ptr = NULL;     // Safe - clearly indicates "no valid address"

Checking for NULL

int *ptr = NULL;

if (ptr == NULL) {
    printf("Pointer is NULL\n");
}

// Or equivalently:
if (!ptr) {
    printf("Pointer is NULL\n");
}

Why Use NULL?

1. Indicate uninitialized state
2. Indicate end of data structure
3. Indicate function failure (return NULL on error)
4. Safe default value (crashes are easier to debug than corruption)

Pointer Arithmetic Basics

When you add/subtract from a pointer, it moves by the size of the pointed-to type.

int arr[5] = {10, 20, 30, 40, 50};
int *ptr = arr;  // Points to arr[0]

printf("%d\n", *ptr);       // 10 (arr[0])
printf("%d\n", *(ptr + 1)); // 20 (arr[1])
printf("%d\n", *(ptr + 2)); // 30 (arr[2])

Visual: Pointer Arithmetic

int arr[5] = {10, 20, 30, 40, 50};
int *ptr = arr;

Memory (int = 4 bytes):
Address:  1000      1004      1008      1012      1016
        ┌─────────┬─────────┬─────────┬─────────┬─────────┐
        │   1020304050    │
        └─────────┴─────────┴─────────┴─────────┴─────────┘
            ↑         ↑         ↑
           ptr     ptr + 1   ptr + 2
          (1000)    (1004)    (1008)

ptr + 1 doesn't add 1 byte, it adds sizeof(int) = 4 bytes!

Type Matters

char *cp = (char *)1000;    // Assume address 1000
int *ip = (int *)1000;

cp + 11001  (moves 1 byte)
ip + 11004  (moves 4 bytes)

Common Mistakes

1. Using Uninitialized Pointers

// WRONG
int *ptr;
*ptr = 42;  // CRASH! ptr points to random location

// RIGHT
int x;
int *ptr = &x;
*ptr = 42;

2. Confusing & and *

int x = 42;
int *ptr = &x;

&x   → address of x (a number like 0x1000)
*ptr → value at address ptr points to (42)
ptr  → the address stored in ptr

// These are equivalent:
x == *ptr     // Both give 42
&x == ptr     // Both give address of x

3. Wrong Type of Pointer

int x = 42;
float *ptr = &x;   // WARNING! Type mismatch

*ptr = 3.14;  // Writes float bit pattern into int memory
              // Result is garbage

4. Dereferencing NULL

int *ptr = NULL;
*ptr = 42;  // CRASH! Segmentation fault

5. Returning Address of Local Variable

int* badFunction(void) {
    int local = 42;
    return &local;    // WRONG! local doesn't exist after function returns
}

Why Use Pointers?

1. Modify Variables in Functions

void increment(int *ptr) {
    (*ptr)++;  // Modifies original variable
}

int main(void) {
    int x = 5;
    increment(&x);
    printf("%d\n", x);  // Output: 6
}

2. Efficient Data Passing

// Without pointer: copies entire struct (slow)
void processData(struct BigStruct data);

// With pointer: passes just an address (fast)
void processData(struct BigStruct *data);

3. Dynamic Memory Allocation

int *arr = malloc(100 * sizeof(int));  // Allocate array at runtime
// ... use arr ...
free(arr);

4. Work with Arrays and Strings

char *str = "Hello";  // Pointer to string
int arr[5];
int *ptr = arr;       // Pointer to array

5. Build Data Structures

struct Node {
    int data;
    struct Node *next;  // Pointer to next node
};

Summary

Key Concepts

ConceptSymbolMeaning
Pointer declarationint *ptrDeclares a pointer to int
Address-of&xGets address of x
Dereference*ptrGets value at address ptr points to
NULL pointerNULLPointer that points to nothing

Pointer Operations

int x = 42;
int *ptr = &x;     // ptr holds address of x

*ptr              // Read value at address (42)
*ptr = 100        // Write value at address (x becomes 100)
ptr + 1           // Address of next int in memory
ptr == NULL       // Check if pointer is null

Memory Picture

Variable x at address 1000:
┌──────────┐
│ x = 42   │
│ @1000    │
└──────────┘

Pointer ptr at address 2000:
┌──────────┐
│ptr= 1000 │ ───→ points to x
│ @2000    │
└──────────┘

&x     = 1000 (address of x)
ptr    = 1000 (value stored in ptr)
*ptr   = 42   (value at address 1000)
&ptr   = 2000 (address of ptr itself)

Golden Rules

✓ Always initialize pointers (to valid address or NULL)
✓ Check for NULL before dereferencing
✓ Use correct pointer type for the data
✓ Don't return addresses of local variables
✓ Free dynamically allocated memory

Next Steps

After understanding pointer basics:

  1. Learn Pointers and Arrays relationship
  2. Study Pointer to Pointer (double pointers)
  3. Explore Dynamic Memory Allocation
  4. Understand Function Pointers

"A pointer is just a variable that holds an address - nothing more, nothing less."

Pointers - C Programming Tutorial | DeepML