Nearby lessons

96 of 124

C - Pointer to Array

A pointer to an arrayint (*p)[5] — points at a whole array rather than a single element. Learn the parenthesised syntax, how arithmetic on it jumps a full array at a time, and why it is the correct type for a 2D array row.

The Parenthesised Declaration

The parentheses bind the * to the name before the [5] applies. Drop them and you get a completely different type:

Example01
CCode Cell
1#include <stdio.h>
2 
3int main()
4{
5 int a[5] = {10, 20, 30, 40, 50};
6 
7 int (*p)[5] = &a; /* pointer to the WHOLE array */
8 int *q = a; /* pointer to the FIRST ELEMENT */
9 
10 printf("sizeof(*p) = %zu (the whole array)\n", sizeof(*p));
11 printf("sizeof(*q) = %zu (one int)\n", sizeof(*q));
12 
13 printf("(*p)[2] = %d\n", (*p)[2]);
14 printf("q[2] = %d\n", q[2]);
15 return 0;
16}
Output
sizeof(*p) = 20  (the whole array)
sizeof(*q) = 4   (one int)
(*p)[2]    = 30
q[2]       = 30

Note the &

To get a pointer to the whole array you need &a, not a. The bare name a decays to int *:

Example02
CCode Cell
1#include <stdio.h>
2 
3int main()
4{
5 int a[5] = {10, 20, 30, 40, 50};
6 
7 int (*p)[5] = &a; /* correct */
8 /* int (*bad)[5] = a; warning: incompatible types */
9 
10 printf("a = %p (an int *)\n", (void *) a);
11 printf("&a = %p (an int (*)[5] - same value, different type)\n",
12 (void *) &a);
13 printf("(*p)[0] = %d\n", (*p)[0]);
14 return 0;
15}
Output
a  = 0x7ffd1c2a3b40 (an int *)
&a = 0x7ffd1c2a3b40 (an int (*)[5] - same value, different type)
(*p)[0] = 10

Arithmetic Jumps a Whole Array

In simple words: pointer arithmetic always moves by the size of what is pointed to. An int * points to a 4-byte int, so +1 moves 4 bytes. An int (*)[5] points to a 20-byte array, so +1 moves 20 bytes — past the entire array.
Example03
CCode Cell
1#include <stdio.h>
2 
3int main()
4{
5 int a[5] = {10, 20, 30, 40, 50};
6 
7 int (*p)[5] = &a;
8 int *q = a;
9 
10 printf("p = %p\n", (void *) p);
11 printf("p + 1 = %p (+20 bytes: a whole array)\n", (void *) (p + 1));
12 
13 printf("q = %p\n", (void *) q);
14 printf("q + 1 = %p (+4 bytes: one int)\n", (void *) (q + 1));
15 return 0;
16}
Output
p     = 0x7ffd1c2a3b40
p + 1 = 0x7ffd1c2a3b54  (+20 bytes: a whole array)
q     = 0x7ffd1c2a3b40
q + 1 = 0x7ffd1c2a3b44  (+4 bytes: one int)

Three Ways to Reach an Element

All of these read the same value. p[0][i] is usually the clearest:

Example04
CCode Cell
1#include <stdio.h>
2 
3int main()
4{
5 int a[5] = {10, 20, 30, 40, 50};
6 int (*p)[5] = &a;
7 int i;
8 
9 for (i = 0; i < 5; i++)
10 printf("(*p)[%d]=%2d p[0][%d]=%2d *(*p + %d)=%2d\n",
11 i, (*p)[i], i, p[0][i], i, *(*p + i));
12 return 0;
13}
Output
(*p)[0]=10  p[0][0]=10  *(*p + 0)=10
(*p)[1]=20  p[0][1]=20  *(*p + 1)=20
(*p)[2]=30  p[0][2]=30  *(*p + 2)=30
(*p)[3]=40  p[0][3]=40  *(*p + 3)=40
(*p)[4]=50  p[0][4]=50  *(*p + 4)=50

Why It Exists — 2D Array Rows

This is the real reason the type matters. For int m[3][4], the name m decays to a pointer to a row — that is, int (*)[4]:

Example05
CCode Cell
1#include <stdio.h>
2 
3int main()
4{
5 int m[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
6 
7 int (*row)[4] = m; /* m already has this exact type */
8 int i, j;
9 
10 for (i = 0; i < 3; i++)
11 {
12 for (j = 0; j < 4; j++)
13 printf("%4d", row[i][j]);
14 printf("\n");
15 }
16 
17 printf("\nm and row have the same type: %s\n",
18 (void *) m == (void *) row ? "yes" : "no");
19 return 0;
20}
Output
   1   2   3   4
   5   6   7   8
   9  10  11  12

m and row have the same type: yes

Walking Rows by Incrementing

Because +1 advances a whole row, you can step through a matrix row by row:

Example06
CCode Cell
1#include <stdio.h>
2 
3int main()
4{
5 int m[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
6 int (*row)[4] = m;
7 int i, j, total;
8 
9 for (i = 0; i < 3; i++, row++) /* row++ moves one full row */
10 {
11 total = 0;
12 for (j = 0; j < 4; j++) total += (*row)[j];
13 printf("Row %d sum = %d\n", i, total);
14 }
15 return 0;
16}
Output
Row 0 sum = 10
Row 1 sum = 26
Row 2 sum = 42

The Correct Function Parameter

int m[][4] and int (*m)[4] are the same parameter type. This is why the column count is mandatory:

Example07
CCode Cell
1#include <stdio.h>
2 
3/* These two headers are identical to the compiler */
4void printA(int m[][4], int rows)
5{
6 for (int i = 0; i < rows; i++)
7 {
8 for (int j = 0; j < 4; j++) printf("%4d", m[i][j]);
9 printf("\n");
10 }
11}
12 
13void printB(int (*m)[4], int rows)
14{
15 for (int i = 0; i < rows; i++)
16 {
17 for (int j = 0; j < 4; j++) printf("%4d", m[i][j]);
18 printf("\n");
19 }
20}
21 
22int main()
23{
24 int m[2][4] = {{1,2,3,4}, {5,6,7,8}};
25 
26 printf("printA:\n"); printA(m, 2);
27 printf("printB:\n"); printB(m, 2);
28 return 0;
29}
Output
printA:
   1   2   3   4
   5   6   7   8
printB:
   1   2   3   4
   5   6   7   8

Why the Column Count Is Required

void f(int m[][], int rows) is a compile error, and for a good reason. To find m[2][1] the compiler computes base + 2 × columns + 1. Without the column count that arithmetic is impossible. The first dimension can be omitted because it never appears in the formula.
Example08
CCode Cell
1#include <stdio.h>
2 
3/* void broken(int m[][], int r) ERROR: incomplete element type */
4/* void broken(int **m, int r) WRONG TYPE for a real 2D array */
5 
6void correct(int m[][3], int rows) /* columns stated - required */
7{
8 for (int i = 0; i < rows; i++)
9 for (int j = 0; j < 3; j++)
10 printf("m[%d][%d] = %d\n", i, j, m[i][j]);
11}
12 
13int main()
14{
15 int m[2][3] = {{1,2,3}, {4,5,6}};
16 correct(m, 2);
17 return 0;
18}
Output
m[0][0] = 1
m[0][1] = 2
m[0][2] = 3
m[1][0] = 4
m[1][1] = 5
m[1][2] = 6

Not the Same as int **

A common confusion. A real 2D array is one contiguous block; an int ** is an array of separate row pointers. They are not interchangeable:

int (*p)[4]int **p
Points toAn array of 4 intsAn int *
MemoryOne contiguous blockSeparate blocks per row
Works with int m[3][4]YesNo
Works with malloc'd rowsNoYes
p + 1 advances16 bytes8 bytes
Example09
CCode Cell
1#include <stdio.h>
2 
3int main()
4{
5 int m[2][4] = {{1,2,3,4}, {5,6,7,8}};
6 
7 int (*correct)[4] = m;
8 /* int **wrong = m; warning: incompatible pointer type
9 Dereferencing it would read 1 as an ADDRESS */
10 
11 printf("correct[1][2] = %d\n", correct[1][2]);
12 printf("The 8 ints are contiguous: ");
13 for (int i = 0; i < 8; i++) printf("%d ", ((int *) m)[i]);
14 printf("\n");
15 return 0;
16}
Output
correct[1][2] = 7
The 8 ints are contiguous: 1 2 3 4 5 6 7 8 

Common Mistakes

  • Omitting the parenthesesint *p[5] is an array of 5 pointers, not a pointer to an array.
  • Assigning a instead of &a — the bare name has type int *.
  • Forgetting to dereferencep[2] on an int (*p)[5] is a whole array, not an element.
  • Mixing up int (*p)[4] and int **p — different memory layouts entirely.
  • Omitting the column count in a parameter — a compile error.
  • Wrong column countint (*p)[3] aimed at an int m[2][4] reads the wrong offsets with no warning at use time.
Trainer's Note: the reliable reading rule is "right when you can, left when you must", starting at the name. For int (*p)[5]: p — the parenthesis blocks going right, so go left: pointer — then right: to an array of 5 — then left: of int. For int *p[5]: p — go right: array of 5 — then left: of pointers to int.
📝 Key Takeaways
  • int (*p)[5] points to an array of 5 ints — the parentheses are essential.
  • p + 1 advances by a whole array, not one element.
  • Access elements with (*p)[i] or p[0][i].
  • For int m[3][4], the expression m has type int (*)[4].
  • Without parentheses, int *p[5] is an array of pointers instead.

🧠 Test Your Knowledge

4 Questions
Progress: 0 / 4