Question

How Many Levels of Pointers Can You Have in C?

ccpppointerslanguage-lawyerlimit

Question

In C, how many pointer indirections (*) are allowed for a single variable declaration?

For example:

int a = 10;
int *p = &a;
int **q = &p;
int ***r = &q;

This pattern can continue with more levels of indirection, such as:

int ****************zz;

Is there a defined limit on how many pointer levels are allowed in C, and what determines that limit?

Short Answer

By the end of this page, you will understand what pointer levels mean in C, how multi-level pointers are declared and used, whether the language defines a maximum depth, and what practical limits exist in real programs and compilers.

Concept

A pointer in C stores the memory address of another object. When you add another *, you create a pointer to a pointer.

  • int *p means p points to an int
  • int **q means q points to a pointer that points to an int
  • int ***r means r points to a pointer that points to a pointer that points to an int

Each extra * adds one more level of indirection.

This matters because C often works directly with memory addresses. Multi-level pointers are useful when:

  • you need a function to modify a pointer variable
  • you work with dynamically allocated arrays of pointers
  • you represent nested structures such as command-line argument lists or strings

Is there a maximum in C?

The C language does not set a small practical number like 2, 3, or 10. In principle, you can keep adding pointer levels as long as the declaration is valid and your compiler can handle it.

However, in practice, limits come from:

  • the compiler's implementation limits
  • parser complexity
  • readability and maintainability
  • whether the type is actually useful

So the real answer is:

  • Conceptually: pointer levels can continue indefinitely
  • Practically: you are limited by your compiler and system resources

In normal code, most programs use only:

  • single pointers: T *
  • double pointers: T **
  • occasionally triple pointers: T ***

Anything beyond that is rare and usually a sign that the design should be simplified.

Mental Model

Think of a pointer like a note that tells you where something is stored.

  • A normal variable is the actual item
  • A pointer is a note with the item's location
  • A pointer to a pointer is a note that tells you where the first note is
  • A pointer to a pointer to a pointer is a note that tells you where the second note is

So each * means: follow one more layer of notes.

Example:

  • a is the real box containing 10
  • p is a note pointing to a
  • q is a note pointing to p
  • r is a note pointing to q

To get the value 10 from r, you must follow three notes:

***r

The more stars you add, the more times you need to follow references to reach the actual value.

Take Quiz

Syntax and Examples

The basic syntax is:

type *name;
type **name;
type ***name;

Example:

#include <stdio.h>

int main(void) {
    int a = 10;
    int *p = &a;
    int **q = &p;
    int ***r = &q;

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

    return 0;
}

Output:

a = 10
*p = 10
**q = 10
***r = 10

What each line means

  • int *p = &a; stores the address of a
  • int **q = &p; stores the address of p
  • int ***r = &q; stores the address of q

When dereferencing:

  • *p gives the int value
  • **q also gives the int value
  • ***r also gives the int value

A larger declaration is still valid syntax if the types match:

int x = 5;
int *p1 = &x;
int **p2 = &p1;
int ***p3 = &p2;
int ****p4 = &p3;

This is legal C, though higher levels are rarely needed.

Step by Step Execution

Trace this example:

#include <stdio.h>

int main(void) {
    int a = 10;
    int *p = &a;
    int **q = &p;

    printf("%d\n", **q);
    return 0;
}

Step 1

int a = 10;

A variable a is created and stores the value 10.

Step 2

int *p = &a;

p stores the address of a.

If a lives at some memory location, p contains that location.

Step 3

int **q = &p;

q stores the address of p.

Now:

  • q points to p
  • p points to a
  • a stores 10

Step 4

printf("%d\n", **q);

Evaluate **q:

  1. *q gives p
  2. **q gives *p
  3. *p gives a
  4. the value of a is 10

So the program prints:

10

This is the key idea of multiple pointer levels: each * follows one more address.

Real World Use Cases

Multi-level pointers are uncommon in beginner code, but they are useful in real C programs.

1. Letting a function modify a pointer

If a function needs to change a pointer variable itself, you pass a pointer to that pointer.

#include <stdlib.h>

void allocate_int(int **ptr) {
    *ptr = malloc(sizeof(int));
    if (*ptr != NULL) {
        **ptr = 42;
    }
}

Here int **ptr lets the function assign a new address to the caller's pointer.

2. Arrays of strings

Command-line arguments are commonly represented as char **argv.

int main(int argc, char **argv) {
    return 0;
}

argv is effectively a pointer to pointers to characters, because each argument is a string.

3. Dynamic 2D data structures

A dynamically allocated array of row pointers may use int **matrix.

int **matrix;

Each element of matrix can point to a row.

4. Linked APIs and output parameters

Some C libraries return complex data through output parameters like T **out so the function can create or replace a pointer value.

Take Quiz

Real Codebase Usage

In real codebases, developers usually keep pointer depth low and use extra levels only when they solve a specific problem.

Common patterns

Guard clauses

When using multi-level pointers, code often checks for NULL early.

if (ptr == NULL || *ptr == NULL) {
    return;
}

This prevents invalid dereferencing.

Output parameters

Functions often use T **out to allocate memory or return a newly created object.

int create_buffer(char **out) {
    *out = malloc(100);
    if (*out == NULL) {
        return -1;
    }
    return 0;
}

Validation before dereferencing

Each pointer level may need validation.

if (q != NULL && *q != NULL) {
    printf("%d\n", **q);
}

Prefer simpler designs

If code reaches ****ptr or deeper, developers often consider:

  • using a struct
  • splitting logic into smaller functions
  • redesigning ownership of memory
  • reducing nested indirection

That is because very deep pointer chains are hard to read, debug, and maintain.

Common Mistakes

1. Confusing declaration with dereference

This declares a pointer:

int *p;

This dereferences a pointer:

*p = 10;

The * symbol is used in both contexts, but the meaning depends on where it appears.

2. Using the wrong number of stars

Broken code:

int a = 10;
int *p = &a;
int *q = &p;

Problem:

  • &p is of type int **
  • but q is declared as int *

Correct version:

int a = 10;
int *p = &a;
int **q = &p;

3. Dereferencing too many or too few times

Broken code:

int a = 10;
int *p = &a;
printf("%d\n", p);

This prints an address using the wrong format, not the integer value.

Correct:

printf("%d\n", *p);

If printing an address, use %p and cast to void *:

printf("%p\n", (void *)p);

4. Dereferencing uninitialized pointers

Broken code:

int **q;
printf("%d\n", **q);

q was never initialized, so dereferencing it is undefined behavior.

5. Forgetting NULL checks in nested pointers

Broken code:

if (**q == 10) {
    printf("ok\n");
}

Safer:

if (q != NULL && *q != NULL && **q == 10) {
    printf("ok\n");
}

6. Assuming more pointer levels mean more power

More indirection does not automatically improve a design. Often it makes the code harder to understand. Use additional levels only when they model a real need.

Comparisons

ConceptMeaningTypical useCommon depth
intStores an integer valueRegular numeric data0
int *Points to an integerPass-by-reference, dynamic memory1
int **Points to a pointer to an integerModify a pointer in a function, 2D structures2
int ***Points to a pointer to a pointerRare, nested ownership or APIs3

Pointer depth vs array syntax

These can look related but mean different things:

int *p;
int arr[10];
  • int *p is a pointer variable
  • int arr[10] is an array of 10 integers

And this:

char **argv;

usually means a pointer to a sequence of string pointers, not a "2D character array" in the strict sense.

Practical comparison

ApproachEasier to read?Flexible?Common in C?
Single pointerYesYesVery common
Double pointerUsuallyYesCommon
Triple pointerLess soSometimesRare
4+ levelsNoRarely usefulVery rare
Take Quiz

Cheat Sheet

Quick reference

Declarations

int a;
int *p;
int **q;
int ***r;

Meaning

  • int -> value
  • int * -> address of an int
  • int ** -> address of an int *
  • int *** -> address of an int **

Example chain

int a = 10;
int *p = &a;
int **q = &p;
int ***r = &q;

Accessing the final value

*p     // 10
**q    // 10
***r   // 10

Main rule

Each * means one more level of indirection.

Is there a language limit?

  • The C standard does not define a small fixed maximum pointer depth for normal usage.
  • Real limits depend on the compiler and implementation.

Practical advice

  • Use T * often
  • Use T ** when a function must modify a pointer
  • Use T *** rarely
  • Avoid deeper levels unless there is a strong reason

Safety checks

Before dereferencing nested pointers:

if (q != NULL && *q != NULL) {
    printf("%d\n", **q);
}

Format specifiers

  • integer value: %d
  • pointer address: %p
printf("%p\n", (void *)p);

FAQ

How many levels of pointers are allowed in C?

C does not define a small fixed number like 5 or 10. You can declare many levels of pointers, but the practical limit depends on the compiler.

Is int ********p; valid C?

Yes, if the declaration is syntactically correct and the compiler supports it.

Why would anyone use a double pointer in C?

A double pointer is commonly used when a function needs to modify a pointer variable, such as allocating memory and storing the result in the caller's pointer.

Are triple pointers used in real programs?

Yes, but rarely. They appear in some APIs, nested data structures, and advanced memory-handling code.

Does more pointer depth use more memory?

A pointer variable uses memory for storing an address. More separate pointer variables mean more memory usage, but the number of * in a type is mainly about type relationships, not automatic extra storage layers.

Is there any benefit to using very deep pointers?

Usually no. Deep pointer chains are hard to read and maintain. They are only helpful when the problem truly requires multiple levels of indirection.

What is the difference between * in a declaration and * in an expression?

In a declaration, * means "this variable is a pointer." In an expression, * means "dereference this pointer to access what it points to."

Take Quiz

Mini Project

Description

Build a small C program that demonstrates one, two, and three levels of pointers by printing both values and addresses. This helps you see how each pointer layer refers to the previous one and when dereferencing is needed.

Goal

Create a program that declares int, int *, int **, and int ***, then prints the stored value through each valid dereference level.

Requirements

  • Declare an integer variable with an initial value.
  • Create a single pointer, double pointer, and triple pointer linked correctly.
  • Print the integer value directly and through each pointer chain.
  • Print at least one address using %p.
  • Keep the program valid and compilable in standard C.
Take Quiz

Keep learning

Related questions

View more