Skip to content

C Pointers — Address-of Operator, Dereferencing, Pointer Types, and NULL

DodaTech Updated 2026-06-28 8 min read

In this tutorial, you will learn about C Pointers. We cover key concepts, practical examples, and best practices to help you master this topic.

C pointers are variables that store memory addresses instead of values. The address-of operator (&) gets a variable's address, and the dereference operator (*) accesses the value at that address.

Why It Matters

Pointers are what make C different from most other languages. They give you direct access to memory, enabling dynamic memory allocation, efficient array processing, and data structures like linked lists and trees. A thorough understanding of pointers separates a competent C programmer from a beginner. Every systems programmer must master pointers.

Real-World Use

The Linux kernel uses pointers extensively for Process scheduling, memory management, and device drivers. Every FILE* you use for file I/O is a pointer to a struct. Function pointers enable callback mechanisms. Durga Antivirus Pro uses pointers to scan memory buffers efficiently.

What You Will Learn

  • What pointers are and how they store addresses
  • The & (address-of) and * (dereference) operators
  • Pointer types and why type matters
  • NULL pointers and how to use them safely
  • Pointers to different data types

Learning Path

flowchart LR
  A[Strings] --> B[Pointers Basics
You are here] B --> C[Pointer Arithmetic] C --> D[Dynamic Memory] D --> E[Structs] style B fill:#f90,color:#fff

What Is a Pointer?

A pointer is a variable that holds a memory address. Instead of storing a value like 42, it stores the address where 42 lives in memory:

#include <stdio.h>

int main() {
    int x = 42;
    int *ptr = &x;  // ptr stores the address of x
    
    printf("Value of x: %d\n", x);      // 42
    printf("Address of x: %p\n", &x);    // e.g., 0x7fff12345678
    printf("Value of ptr: %p\n", ptr);   // same address as &x
    printf("Value at ptr: %d\n", *ptr);  // 42 (dereference)
    
    // Change x through the pointer
    *ptr = 100;
    printf("New value of x: %d\n", x);  // 100
    
    return 0;
}

Expected output (addresses will vary):

Value of x: 42
Address of x: 0x7fff12345678
Value of ptr: 0x7fff12345678
Value at ptr: 42
New value of x: 100

The Address-of Operator (&)

The & operator returns the memory address of a variable:

#include <stdio.h>

int main() {
    int a = 10;
    double b = 3.14;
    char c = 'Z';
    
    printf("Address of a: %p\n", &a);
    printf("Address of b: %p\n", &b);
    printf("Address of c: %p\n", &c);
    
    // Addresses are typically 4 or 8 bytes apart
    // depending on type sizes
    
    return 0;
}

The Dereference Operator (*)

The * operator accesses the value stored at a pointer's address. In declarations, * indicates a pointer type. In expressions, * dereferences the pointer:

#include <stdio.h>

int main() {
    int x = 42;
    int *ptr;  // Declaration: ptr is a pointer to int
    
    ptr = &x;  // ptr now holds the address of x
    
    // Dereference: access the value at the address
    int y = *ptr;  // y gets the value 42
    
    printf("x = %d, y = %d\n", x, y);
    
    // Modify through pointer
    *ptr = 99;  // Changes x to 99
    printf("x = %d\n", x);
    
    return 0;
}

Expected output:

x = 42, y = 42
x = 99

Pointer Types

Every pointer has a type that determines what kind of data it points to:

#include <stdio.h>

int main() {
    int i = 10;
    double d = 3.14;
    char c = 'A';
    
    int *ip = &i;      // Pointer to int
    double *dp = &d;   // Pointer to double
    char *cp = &c;     // Pointer to char
    
    printf("int pointer: %p, value: %d\n", ip, *ip);
    printf("double pointer: %p, value: %.2f\n", dp, *dp);
    printf("char pointer: %p, value: %c\n", cp, *cp);
    
    // Pointer size (same for all types on a given platform)
    printf("Size of int*: %zu bytes\n", sizeof(int*));
    printf("Size of double*: %zu bytes\n", sizeof(double*));
    printf("Size of char*: %zu bytes\n", sizeof(char*));
    
    // All pointer types have the same size
    // because they all store addresses
    
    return 0;
}

Expected output (addresses vary):

int pointer: 0x7fff12345670, value: 10
double pointer: 0x7fff12345678, value: 3.14
char pointer: 0x7fff12345680, value: A
Size of int*: 8 bytes
Size of double*: 8 bytes
Size of char*: 8 bytes

Even though the data types have different sizes (4, 8, 1 respectively), all pointers on a 64-bit system are 8 bytes because they all store addresses.

Why Pointer Types Matter

The type of a pointer matters for two reasons:

  1. Dereferencing: The compiler needs to know how many bytes to read when you dereference
  2. Pointer arithmetic: Adding 1 to an int* moves 4 bytes; adding 1 to a char* moves 1 byte
#include <stdio.h>

int main() {
    int arr[] = {10, 20, 30, 40, 50};
    int *ip = arr;
    char *cp = (char*)arr;
    
    printf("int pointer: %p -> %d\n", ip, *ip);
    printf("char pointer: %p -> %d\n", cp, *cp);
    
    ip++;  // Moves 4 bytes forward
    cp++;  // Moves 1 byte forward
    
    printf("After increment:\n");
    printf("int pointer: %p -> %d\n", ip, *ip);
    printf("char pointer: %p -> %d\n", cp, *cp);
    
    return 0;
}

NULL Pointers

A NULL pointer points to nothing. It is used to indicate that a pointer does not currently hold a valid address:

#include <stdio.h>

int main() {
    int *ptr = NULL;  // Pointer points to nothing
    
    // Always check for NULL before dereferencing
    if (ptr != NULL) {
        printf("Value: %d\n", *ptr);
    } else {
        printf("Pointer is NULL, cannot dereference.\n");
    }
    
    // Dereferencing NULL causes a crash (segmentation fault)
    // *ptr = 42;  // CRASH!
    
    // NULL is defined as ((void*)0) in standard libraries
    printf("NULL value: %d\n", NULL == 0);  // 1 (true)
    
    return 0;
}

When to Set Pointers to NULL

  • After declaring a pointer that is not immediately assigned
  • After freeing memory (to prevent dangling pointer)
  • Before checking if a pointer is valid

The void* Pointer

void* is a generic pointer that can point to any data type. It cannot be dereferenced directly:

#include <stdio.h>

int main() {
    int x = 42;
    double y = 3.14;
    
    void *vp = &x;  // void* can hold any address
    printf("void* to int: %p\n", vp);
    
    // Cannot dereference void* directly:
    // printf("%d", *vp);  // ERROR!
    
    // Must cast back to the correct type
    printf("Value: %d\n", *(int*)vp);
    
    vp = &y;  // Now points to double
    printf("Value: %.2f\n", *(double*)vp);
    
    return 0;
}

void* is used in standard library functions like malloc(), qsort(), and memcpy() because they work with any data type.

Pointers and Function Arguments

Pointers allow functions to modify variables outside their scope:

#include <stdio.h>

// Pass by value -- does NOT modify the original
void increment_bad(int x) {
    x++;
}

// Pass by pointer -- modifies the original
void increment(int *x) {
    (*x)++;
}

int main() {
    int value = 10;
    
    increment_bad(value);
    printf("After bad increment: %d\n", value);  // 10
    
    increment(&value);
    printf("After pointer increment: %d\n", value);  // 11
    
    return 0;
}

Expected output:

After bad increment: 10
After pointer increment: 11

Common Mistakes

1. Dereferencing Uninitialized Pointers

int *ptr;
*ptr = 42;  // Dereferencing garbage address -- crash!

Always initialize pointers to NULL or a valid address.

2. Forgetting the & When Passing to scanf

int x;
scanf("%d", x);   // Wrong: x is not a pointer
scanf("%d", &x);  // Correct: &x is the address of x

3. Confusing Declaration with Dereference

int *p;    // Declaration: p is a pointer to int
*p = 42;   // Dereference: store 42 at the address in p (uninitialized!)

In declarations, * indicates pointer type. In expressions, * dereferences.

4. Using NULL Pointers

int *p = NULL;
*p = 42;  // Segmentation fault

Always check for NULL before dereferencing.

5. Confusing Pointer with Pointee Type

int *p;
int *q;
p = q;     // Copies the address, not the value
*p = *q;   // Copies the value at the address

Practice Questions

  1. What does & do in C? It returns the memory address of a variable.

  2. What does * do when used in front of a pointer? It dereferences the pointer, accessing the value at the stored address.

  3. What is a NULL pointer? A pointer that points to nothing (address 0). It is used to indicate an invalid or uninitialized pointer.

  4. Why do all pointer types have the same size? Because they all store memory addresses, which have a fixed size (4 bytes on 32-bit, 8 bytes on 64-bit).

  5. Challenge: Write a program that swaps two integers using pointers (a swap function).

Mini Project: Swap Function

#include <stdio.h>

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

int main() {
    int x = 10, y = 20;
    
    printf("Before swap: x = %d, y = %d\n", x, y);
    swap(&x, &y);
    printf("After swap:  x = %d, y = %d\n", x, y);
    
    return 0;
}

FAQ

What is the difference between a pointer and a variable?

A variable stores a value. A pointer stores a memory address where a value lives. The pointer 'points to' the variable.

Can a pointer change what it points to?

Yes. Pointers are variables, so their value (the address they hold) can change. ptr = &x; ptr = &y; is perfectly valid.

What happens if I dereference NULL?

Undefined behavior, almost always a segmentation fault. The program crashes. Always check for NULL before dereferencing.

Why do I need the & in scanf?

scanf needs to modify the variable, so it requires a pointer (address). Without &, you pass the value, and scanf cannot store the input.

Is void* the same as a generic pointer?

Yes. void* can point to any type, but cannot be dereferenced without casting. It is used for generic functions like malloc and qsort.

What is Next

Now that you understand pointer basics, proceed to Pointer Arithmetic to learn how to navigate arrays using pointers.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro

Home Browse C