Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 28

Pointers to Arrays vs. Arrays of Pointers

Recommended resources

Hello! Welcome back.

In our last lesson, we explored double pointers (**p), focusing on their power to let a function modify a pointer owned by its caller. We saw this in action with functions that allocate memory and in an elegant linked-list removal algorithm. We also noted that char **argv is a common sight in C, hinting at a deep relationship between pointers and arrays.

Today, we're going to dissect that relationship. The syntax around multi-level pointers and arrays can be one of the most confusing parts of C. This lesson aims to bring clarity by tackling a fundamental distinction head-on. By the end of our session, you will be able to confidently distinguish between a pointer to an array and an array of pointers in both declaration and usage.

The Crux of the Matter: Declaration and Precedence

At first glance, the declarations for these two concepts look deceptively similar. The entire difference boils down to one pair of parentheses and a deep-seated rule in C: operator precedence.

Consider these two declarations:

  1. int *p_arr[10]; // An array of pointers
  2. int (*arr_p)[10]; // A pointer to an array

Why are they different? The C compiler prioritizes the array subscript operator [] over the pointer dereference operator *.

  • Case 1: int *p_arr[10];

    1. Because [] has higher precedence, the compiler first reads p_arr[10], identifying p_arr as an array of 10 elements.
    2. Then, it looks at the rest of the type specifier: int *.
    3. Putting it together: p_arr is an array of 10 pointers to int.
  • Case 2: int (*arr_p)[10];

    1. The parentheses () override the default precedence, forcing the compiler to evaluate *arr_p first. This identifies arr_p as a pointer.
    2. A pointer to what? The compiler then sees [10], meaning an array of 10 elements.
    3. Finally, it sees the base type int.
    4. Putting it together: arr_p is a pointer to an array of 10 ints.

This rule is the foundation. To help you parse even more complex declarations, there are a couple of helpful techniques.

C pointer to array/array of pointers disambiguation

This Stack Overflow discussion is an excellent resource. We will refer back to it, but for now, let's focus on the rules for reading declarations.

Read the answers from users 'NAND' and 'nishantbhardwaj2002'. 'NAND' introduces the 'Right-Left Rule' for parsing declarations, which is a highly effective mnemonic. 'nishantbhardwaj2002' provides a concise explanation of how operator precedence drives the interpretation of these declarations. Focus on understanding why the parentheses are so important.

Now, let's explore each of these concepts with practical examples.


1. Array of Pointers

An array of pointers is exactly what it sounds like: an array where each element is a pointer. Each pointer can hold the address of a different variable or memory location.

Memory Representation of an Array of Pointers
This diagram shows an array of pointers named `s_ptr`. Each element, `s_ptr[0]`, `s_ptr[1]`, etc., is a pointer variable that stores the memory address of a separate string literal. Note that the strings themselves are not stored contiguously.

This structure is extremely common for storing a list of strings. Your experience with command-line arguments in main(int argc, char *argv[]) is a perfect example. Here, argv is an array of character pointers, where each pointer points to a C-string representing one command-line argument. When argv is passed to a function, it decays into a pointer to its first element (a char*), resulting in the type char**. This is the direct link back to our last lesson on double pointers.

To see this in action, let's watch a video that visualizes the memory layout for an array of strings.

C Arrays and Pointers to Pointers

The video 'C Arrays and Pointers to Pointers' by Kris Jordan provides a fantastic, detailed walkthrough of declaring an array of pointers and visualizing its memory.

Watch from 23:42 to 32:52. The presenter does an excellent job of: Parsing the declaration char *words[]. Drawing the memory layout, showing how words is an array of addresses, with each address pointing to a different string. Demonstrating how a double pointer (char **) can be used to iterate through this array of pointers.

The key takeaway is that an array of pointers provides flexibility. The things being pointed to (the strings, in this case) do not need to be in a single, contiguous block of memory. This is also the fundamental concept behind creating a "jagged array" or a dynamic 2D array, which you'll implement later in this course.


2. Pointer to an Array

A pointer to an array is different. It is a single pointer variable that is specifically typed to hold the address of an entire array.

This is a more subtle concept, but it's critical for understanding pointer arithmetic and how C handles multi-dimensional arrays. The most tangible way to grasp the difference is to see what happens when you perform pointer arithmetic.

Let's say you have int arr[5];.

  • The expression arr (by itself) decays into a pointer to the first element. Its type is int *. If you calculate arr + 1, you get the address of the second element, &arr[1]. The address increases by sizeof(int).
  • The expression &arr gives you the address of the entire array. Its type is int (*)[5]. It points to the same memory location as arr, but its type is different. If you calculate (&arr) + 1, you are telling the compiler to move forward by the size of the entire object it points to—the whole array! The address increases by sizeof(int) * 5.

This video illustrates this distinction perfectly.

array vs &array Pointers Difference Explained | C Programming Tutorial

The video 'array vs &array Pointers Difference Explained' by Portfolio Courses is the best way to visualize this concept.

Please watch from 00:22 to 05:16. The video will show you: How an array name decays to a pointer to the first element. How &array gives you a pointer to the whole array, even though the address value is the same. The crucial difference when you add 1 to each pointer type, demonstrating how the compiler uses the type to determine the size of the jump.

This concept is essential when working with C's native multi-dimensional arrays. A 2D array, like int matrix[3][5], is stored as a contiguous block of 15 integers. When you use the name matrix, it decays into a pointer to the first row. A row is an array of 5 integers, so the type of matrix is int (*)[5]—a pointer to an array of 5 integers. This is why you can pass a 2D array to a function and still use the [row][col] syntax inside that function, provided the function signature correctly declares the pointer type, e.g., void func(int m[][5]) or void func(int (*m)[5]).

Test your understanding!

Given the following declarations on a 64-bit system (where pointers are 8 bytes):

int arr[10];
int *p_arr[10];
int (*arr_p)[10];
  1. What is the value of sizeof(arr)?
  2. What is the value of sizeof(p_arr)?
  3. What is the value of sizeof(arr_p)?
  4. If arr_p points to arr, what is the address difference between arr_p and arr_p + 1 in bytes?
Show answer
  1. sizeof(arr) is 40 bytes. arr is an array of 10 integers. 10 * sizeof(int) = 10 * 4 = 40.
  2. sizeof(p_arr) is 80 bytes. p_arr is an array of 10 pointers. 10 * sizeof(int *) = 10 * 8 = 80.
  3. sizeof(arr_p) is 8 bytes. arr_p is a single pointer variable, regardless of what it points to. Its size is the size of a memory address on the system.
  4. The address difference is 40 bytes. arr_p is a pointer to an array of 10 integers. Incrementing it moves the pointer forward by the size of the entire object it points to, which is sizeof(arr) or 10 * sizeof(int).

Summary Table

To consolidate, here is a side-by-side comparison.

Feature Array of Pointers (type *name[N]) Pointer to an Array (type (*name)[N])
Declaration int *p_arr[10]; int (*arr_p)[10];
Core Concept An array where each of the N elements is a pointer. A single pointer that points to an entire array of N elements.
Memory A contiguous block of N pointer-sized variables. The data they point to can be located anywhere. A single pointer-sized variable. It points to a contiguous block of N data elements.
sizeof(name) N * sizeof(pointer) sizeof(pointer)
Pointer Arithmetic p_arr + 1 points to p_arr[1]. (Moves by sizeof(pointer)). arr_p + 1 points to the memory address immediately after the entire array it points to. (Moves by N * sizeof(type)).
Common Use Arrays of strings, command-line args, jagged arrays. Passing multi-dimensional arrays to functions, iterating through rows of a 2D array.

Conclusion

Mastering the distinction between an array of pointers and a pointer to an array is a significant step toward advanced C proficiency. It moves beyond simply using pointers to thinking about the types of pointers and how those types dictate compiler behavior, especially with sizeof and pointer arithmetic. Your work with AUTOSAR communication stacks and middleware often involves manipulating complex, pre-defined data buffers where understanding these precise memory layouts is not just academic but essential for correctness and safety.

Key Takeaways:

  • Operator precedence is key: [] is higher than *. Parentheses () are used to override this, creating a pointer to an array.
  • An array of pointers (int *p[N]) is a collection of addresses. It's great for managing non-contiguous data blocks, like an array of strings.
  • A pointer to an array (int (*p)[N]) is a single pointer typed to an entire array. Its type is critical for pointer arithmetic, where an increment jumps by the size of the whole array. This is fundamental to how C handles multi-dimensional arrays.

Preview of the Next Lesson:
We've had a taste of parsing int (*arr_p)[10] using the "Right-Left Rule". But what happens when functions get involved? Declarations like void (*(*f[])())() are valid C, though admittedly terrifying. In our next lesson, we will focus exclusively on this skill and learn to parse complex C declarations involving pointers, arrays, and functions using the 'right-left rule', turning you into a human cdecl.