Question

C Pointer to Array vs Array of Pointers: How to Read Complex Declarations

carrayspointersvariable-declaration

Question

What is the difference between the following C declarations?

int *arr1[8];
int (*arr2)[8];
int *(arr3[8]);

Also, what is the general rule for understanding more complex C declarations?

Short Answer

By the end of this page, you will understand how to tell apart an array of pointers from a pointer to an array in C. You will also learn a reliable rule for reading complex C declarations, so syntax like int *arr1[8] or int (*arr2)[8] becomes much easier to decode.

Concept

C declarations can look confusing because symbols like *, [], and () combine in ways that change meaning.

The key idea is that arrays and pointers are different types, even though they are often related.

Consider these declarations:

int *arr1[8];
int (*arr2)[8];
int *(arr3[8]);

What each one means

int *arr1[8];

arr1 is an array of 8 elements, where each element is a pointer to int.

  • arr1[0] is an int *
  • arr1[1] is an int *
  • and so on

int (*arr2)[8];

arr2 is a pointer to an array of 8 integers.

  • arr2 points to one whole array
  • *arr2 is the actual array of 8 int
  • (*arr2)[0] is the first int in that array

int *(arr3[8]);

This is the same as:

int *arr3[8];

So arr3 is also an array of 8 pointers to int.

The parentheses around arr3[8] do not change the meaning here because [] already binds tightly.

Why this matters

In real C programs, you often work with:

  • arrays of strings: char *names[10]
  • tables of row pointers
  • pointers to fixed-size buffers
  • function pointers and callback tables

If you cannot read declarations confidently, it becomes hard to understand APIs, debug type errors, or pass data correctly between functions.

Core truth to remember

  • int *a[8] means array first, then pointer → array of pointers
  • int (*a)[8] means pointer first because parentheses force it → pointer to array

Mental Model

Think of C declarations like reading a road sign from the variable name outward.

Start at the variable name, then look:

  • to the right first if possible
  • then to the left
  • use parentheses as barriers that group things together

Analogy

Imagine the variable name is a person in the middle of a maze.

  • If the first thing they touch is [], they are an array.
  • If the first thing they touch is *, they are a pointer.
  • Parentheses can change which path they must take first.

For example:

int *arr1[8];

Start at arr1:

  • right: [8] → arr1 is an array of 8
  • left: * → whose elements are pointers
  • base type: int

So: array of 8 pointers to int.

Now:

int (*arr2)[8];

Start at arr2:

  • immediate left is inside parentheses: * → arr2 is a pointer
  • then move out right: [8] → pointer to an array of 8
  • base type: int

So: pointer to array of 8 int.

Take Quiz

Syntax and Examples

Core syntax

int *a[8];     // array of 8 pointers to int
int (*b)[8];   // pointer to array of 8 int

Example 1: Array of pointers

#include <stdio.h>

int main(void) {
    int x = 10, y = 20, z = 30;
    int *arr[3] = { &x, &y, &z };

    printf("%d\n", *arr[0]);
    printf("%d\n", *arr[1]);
    printf("%d\n", *arr[2]);

    return 0;
}

arr is an array. Each element stores the address of an int.

  • arr[0] is int *
  • *arr[0] is the int value stored at that address

Example 2: Pointer to an array

#include <stdio.h>

int main(void) {
    int values[3] = { 10, 20, 30 };
    int (*ptr)[3] = &values;

    printf("%d\n", (*ptr)[0]);
    printf("%d\n", (*ptr)[1]);
    printf("%d\n", (*ptr)[2]);

    return 0;
}

Here:

  • values is an array of 3 int
  • &values is a pointer to the whole array
  • ptr stores that address
  • (*ptr)[1] gets the second element

Why parentheses matter

Compare:

int *a[5];
int (*b)[5];

These are completely different types.

  • a is an array containing 5 pointers
  • b is one pointer that points to one array containing 5 integers

Step by Step Execution

Consider this example:

#include <stdio.h>

int main(void) {
    int numbers[4] = { 5, 10, 15, 20 };
    int (*p)[4] = &numbers;

    printf("%d\n", (*p)[2]);
    return 0;
}

Step-by-step

1. Declare an array

int numbers[4] = { 5, 10, 15, 20 };

numbers is an array of 4 integers.

2. Declare a pointer to the whole array

int (*p)[4] = &numbers;

Read it from p:

  • * means pointer
  • [4] means to an array of 4
  • int means array elements are integers

So p points to the whole array numbers.

3. Access the third element

(*p)[2]

Break it down:

  • p is a pointer to the array
  • *p is the array itself
  • (*p)[2] is element at index 2
  • value is 15

Compare with an array of pointers

#include <stdio.h>

int main(void) {
    int a = 5, b = 10, c = 15, d = 20;
    int *p[4] = { &a, &b, &c, &d };

    printf("%d\n", *p[2]);
    return 0;
}

Here:

  • p[2] is a pointer to c
  • *p[2] is the value 15

The output may match, but the types and memory layout are different.

Real World Use Cases

Where array-of-pointers is used

List of strings

char *names[3] = { "Alice", "Bob", "Carol" };

Each element points to the first character of a string.

Sparse or flexible row storage

You can store pointers to separately allocated rows in a table-like structure.

int *rows[10];

Each row can point to a different memory block.

Lookup tables

An array of pointers is useful when each entry refers to another object or buffer.

Where pointer-to-array is used

Passing fixed-size arrays to functions

void print_row(int (*row)[8]);

This says the function expects a pointer to an array of 8 integers.

Working with multidimensional arrays

int matrix[5][8];
int (*row)[8] = matrix;

Each increment of row moves by one full row of 8 integers.

Buffer processing with known dimensions

When a function must preserve row width, a pointer to array is often safer and clearer than flattening everything manually.

Take Quiz

Real Codebase Usage

In real codebases, developers use these forms to make intent explicit.

Common patterns

1. Function parameters for fixed-width rows

void process_rows(size_t count, int (*rows)[8]) {
    for (size_t i = 0; i < count; i++) {
        printf("%d\n", rows[i][0]);
    }
}

This is common with matrices, image data, and protocol packets.

2. Arrays of pointers for dynamic collections

char *errors[5];

Useful when each element may point to a different string or object.

3. Validation and guard clauses

void print_first(int (*arr)[8]) {
    if (arr == NULL) {
        return;
    }
    printf("%d\n", (*arr)[0]);
}

Pointer checks are common before dereferencing.

4. APIs that distinguish contiguous memory from indirect storage

  • int (*p)[8] suggests one contiguous block shaped like an array of 8 int
  • int *p[8] suggests 8 independent pointers, possibly to unrelated memory

That distinction matters for correctness, performance, and iteration logic.

Common Mistakes

1. Assuming arrays and pointers are always the same

They are related, but not identical types.

Broken thinking:

int *a[8];
int (*b)[8];

b = a;   // wrong: incompatible types

Why it fails:

  • a is an array of pointers
  • b is a pointer to an array of int

2. Ignoring parentheses

int *a[8];    // array of pointers
int (*a)[8];  // pointer to array

A small change in parentheses creates a different type.

3. Dereferencing the wrong thing

Broken example:

int values[3] = {1, 2, 3};
int (*p)[3] = &values;
printf("%d\n", *p[1]);

Why this is wrong:

  • p[1] means move to the next array of 3 integers
  • that goes past the one array you actually have

Correct:

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

4. Confusing &array with array

int values[3];
  • values often behaves like pointer to first element: int *
  • &values is pointer to whole array: int (*)[3]

These are not the same type.

5. Reading declarations left to right only

That approach breaks quickly in C.

Instead:

  • start at the variable name
  • go right when possible
  • then go left
  • respect parentheses

Comparisons

DeclarationMeaningTypical use
int *a[8]array of 8 pointers to intlist of addresses, strings, rows
int (*a)[8]pointer to array of 8 intfixed-size arrays, matrix rows
int a[8]array of 8 intdirect storage of integers
int *apointer to intpoints to one int or first element

array of pointers vs pointer to array

FeatureArray of pointersPointer to array
Exampleint *a[8]int (*a)[8]
Number of pointers stored81
Underlying structure8 separate pointer elementsone pointer to one whole array
Memory layoutpointers may refer to unrelated placesrefers to contiguous array block
Access pattern*a[i](*a)[i]

Equivalent declarations

These mean the same thing:

int *arr1[8];
int *(arr3[8]);

Parentheses around arr3[8] do not change the binding in this case.

Take Quiz

Cheat Sheet

Quick reading rule

Start at the variable name:

  1. Look right first
  2. Then look left
  3. Parentheses control grouping
  4. [] and () bind tighter than *

Common patterns

int *a[8];        // array of 8 pointers to int
int (*a)[8];      // pointer to array of 8 int
int (*f)(void);   // pointer to function returning int
int *f(void);     // function returning pointer to int

Your declarations

int *arr1[8];     // array of 8 pointers to int
int (*arr2)[8];   // pointer to array of 8 int
int *(arr3[8]);   // same as int *arr3[8]

Important precedence

  • [] has high precedence
  • () has high precedence
  • * has lower precedence

So:

int *a[8];

is read as:

  • a[8] first
  • then *

Useful type facts

int values[8];
int *p1 = values;      // pointer to first element
int (*p2)[8] = &values; // pointer to whole array

Edge case to remember

array and &array may have the same address value, but they have different types:

values      // type like int * in many expressions
&values     // type int (*)[8]

FAQ

What is the difference between int *a[8] and int (*a)[8] in C?

int *a[8] is an array of 8 pointers to int. int (*a)[8] is a pointer to one array of 8 integers.

Are int *arr1[8] and int *(arr3[8]) the same?

Yes. They mean the same thing: an array of 8 pointers to int.

Why do parentheses matter in C declarations?

Parentheses change grouping. Since [] binds more tightly than *, parentheses can force * to apply first.

How do I read complex C declarations correctly?

Start at the variable name, read to the right when possible, then to the left, and use parentheses to determine grouping.

Is an array the same as a pointer in C?

No. Arrays and pointers are different types. Arrays often decay to pointers in expressions, but they are not identical.

What type is &arr if arr is int arr[8]?

Its type is int (*)[8], which means pointer to array of 8 integers.

When should I use a pointer to an array?

Use it when you want to refer to a whole fixed-size array, especially for multidimensional arrays or APIs with known dimensions.

Take Quiz

Mini Project

Description

Build a small C program that demonstrates the difference between an array of pointers and a pointer to an array. This helps you see that similar-looking declarations can represent very different memory structures and access patterns.

Goal

Create and run a program that prints values using both int *arr[3] and int (*ptr)[3], and observe how access syntax differs.

Requirements

  • Declare an array of 3 integers and a pointer to that array.
  • Declare three separate integers and an array of 3 pointers to them.
  • Print all values from both structures.
  • Use the correct syntax for each case: (*ptr)[i] and *arr[i].
Take Quiz

Keep learning

Related questions

View more