Continuing from the previous article, we will further discuss pointers in the C language.In C, pointers and arrays are two closely related but often confused concepts. When combined, they form two powerful tools:Pointer Arrays and Array Pointers.
1. Pointer Arrays: A Tool for Managing Collections of Data
1.1 What is a Pointer Array?
A pointer array is an array where each element is a pointer. The declaration format is:<span>type *array_name[size]</span>.
-
First, a pointer array is an array. This means it occupies a block of contiguous memory. This array has a fixed size, and we can access each element through indexing (
<span>names[0]</span>,<span>names[1]</span>). -
Second, its element type is a pointer. This is key. The array does not store actual data (like characters, integers, or structures), but rather the addresses pointing to this data. You can think of the array as an apartment building, where each room (array element) does not house a person, but rather a note saying “someone lives at XX address” (pointer).
For example, the following declaration:
// Declare an array containing 4 character pointers
const char *names[] = {"Xiao Ming", "Xiao Hong", "Xiao Gang", "Xiao Mei"};
-
<span>names</span>is an array. -
Each of its elements, namely
<span>names[0]</span>,<span>names[1]</span>, etc., has the type<span>const char *</span>(pointer to constant characters). -
The initialization list contains
<span>"Xiao Ming"</span>,<span>"Xiao Hong"</span>, etc., which are string literals, stored in the read-only data segment (commonly referred to as the<span>.rodata</span>segment). -
Thus, the initialization process of the array is: assigning the memory address of the string
<span>"Xiao Ming"</span>in the read-only data segment to<span>names[0]</span>, assigning the address of<span>"Xiao Hong"</span>to<span>names[1]</span>, and so on.
1.2 Memory Layout and Working Principle
The overall layout of a pointer array in memory is as follows:
Assuming the addresses are simplified, the structure in memory might look like this:
The working principle of a pointer array is as follows: When the program executes <span>printf("%s", names[0]);</span>:
-
The CPU finds the starting address of the
<span>names</span>array, for example,<span>0x1000</span>. -
Using the index
<span>[0]</span>, it calculates the address of the first element:<span>0x1000 + (0 * sizeof(pointer)) = 0x1000</span>. -
Dereferencing this address retrieves the stored value:
<span>0x2000</span>. This value is not the data itself, but a pointer to the data. -
The CPU then jumps to address
<span>0x2000</span>. -
Starting from
<span>0x2000</span>, it reads characters byte by byte until it encounters the string terminator<span>\0</span>, then outputs these characters to the screen. Thus, it prints<span>"Xiao Ming"</span>.
This process is called “address access”, where the pointer array acts as an intermediary directory or routing table.
1.3 Advantages and Applicable Scenarios
-
Efficient storage of data of varying lengths (especially strings):
-
Imagine if you used a two-dimensional character array instead of a pointer array:
<span>char names[4][10] = {"Xiao Ming", "Xiao Hong", ...};</span>. You must specify the second dimension (the maximum length of each string) to be the maximum value among all possible strings, such as 10. For shorter strings like “Xiao Hong” (which occupies 5 bytes: 3 Chinese characters + 1<span>\0</span>), you waste 5 bytes of space. In contrast, a pointer array only stores addresses (typically 8 bytes), and the strings themselves occupy only as much space as they need, making the storage of pointers very compact.
Memory savings:
-
As mentioned above, it avoids the waste of reserving maximum space for each element. When managing a large number of strings of varying lengths, the memory savings can be substantial.
High flexibility:
-
Efficient sorting: If you want to sort a collection of strings alphabetically, swapping strings in a two-dimensional array requires a lot of memory copying (
<span>strcpy</span>). In contrast, swapping elements in a pointer array only requires swapping the values of two pointers (two addresses), which is several orders of magnitude faster and does not affect the original string storage locations. -
Dynamic replacement: You can easily point a pointer to a new string without moving the original string. For example,
<span>names[0] = "Da Ming";</span>, this statement simply changes the address stored in<span>names[0]</span>from<span>0x2000</span>(the address of Xiao Ming) to the address of the new string<span>"Da Ming"</span>. This is very efficient.
Classic Applications
-
Error Message Table: Managing with a pointer array is very intuitive, error codes can be directly used as indices (
<span>error_messages[ERR_NO_MEMORY]</span><code><span>), making the code highly readable.</span> -
Command Line Arguments
<span>argv</span>: The operating system parses user input parameters into strings and then creates a pointer array (<span>char *argv[]</span>) to store the addresses of these strings, passing the address of the array to the<span>main</span>function. The lengths of the parameters vary, and a pointer array is the only natural way to store them.
1.4 Dynamic Memory Allocation Example – Expanding from Static to Dynamic

-
<span>char **arr</span>:<span>arr</span>is a pointer to a pointer. It can be used as the name of a pointer array. -
<span>malloc(size * sizeof(char *))</span>: Allocates a contiguous block of memory on the heap, sized for<span>size</span>pointers. This memory serves the same purpose as the previously statically defined<span>names</span>array, used to store<span>char *</span>type pointers. At this point, each pointer in the array is uninitialized (possibly garbage values).

-
This function allocates another block of memory on the heap for each pointer in the first dimension of the array arr[i] to store the actual string data.
-
This forms a dynamic two-dimensional structure that is much more flexible than a static two-dimensional array. You can decide at runtime how many rows this “array” has (through the first
<span>malloc</span>of<span>size</span>), and how long each row is (through the second<span>malloc</span>, allowing each string to have a different length).
1.5 Common Pitfalls and Solutions – Critical Details
Pitfall 1: Modifying String Literals This is a very common mistake in C.
-
Root Cause: String literals like
<span>"Alice"</span>are not mandated by the C standard to be stored in read-only memory, but almost all modern compilers do so for optimization and sharing. Attempting to modify read-only memory will cause the program to crash (e.g., Segment Fault). -
Solution 1 (Using const):
<span>const char *names[] = {...};</span>This is defensive programming. The<span>const</span>keyword tells the compiler: “The things these pointers point to are constants and cannot be modified.” If you try to write<span>names[0][0] = 'E';</span>, the compiler will report an error at compile time, preventing runtime disasters. This is the preferred method if you do not need to modify these strings. -
Solution 2 (Using Array Initialization):
<span>char name1[] = "Alice";</span>This line does something entirely different. It creates a character array named<span>name1</span>on the stack (if it is a local variable) and copies the string<span>"Alice"</span>(including the terminator<span>\0</span>) into this array to initialize it. The contents of this array are fully modifiable. Then, use the address of this modifiable array to initialize the pointer array. This is a strategy of “sacrificing some space (each string has a copy) for modifiability”.
Pitfall 2: Memory Leaks This is a core issue in dynamic memory management.
-
Root Cause: Every byte of heap memory allocated from the system (via
<span>malloc</span>,<span>calloc</span>, etc.) must have a corresponding<span>free</span>to return it. If you only<span>free</span>the pointer array itself (<span>array</span>), but forget to release the memory pointed to by each pointer in the array (<span>array[0]</span>,<span>array[1]</span>, …), then the memory occupied by these strings can never be reused by the program, causing a memory leak. For long-running programs (like servers or daemons), even small leaks can accumulate and lead to memory exhaustion. -
Release Principle: Release in reverse order. The order of release should be the reverse of the order of allocation. First, release the “leaves” (the deepest data), then release the “branches” (the structures containing pointers).
-
<span>for</span>loop: Release the memory of each pointer pointing to the string (<span>free(array[i])</span>). -
Finally, release the array itself that stores these pointers (
<span>free(array)</span>).
If you only perform step 2 without step 1, then the memory of all strings will be leaked. If you perform step 2 before step 1, then the <span>array[i]</span> in step 1 has already become a dangling pointer, and accessing it again to <span>free</span> will lead to undefined behavior (most likely a crash). Therefore, the order is crucial.
2. Array Pointers: Professional Tools for Handling Multi-Dimensional Arrays
2.1 What is an Array Pointer? – A Pointer to an Entire Array
An array pointer is a pointer that points to an entire array. The declaration format is:<span>type (*pointer_name)[size]</span>
This definition sharply contrasts with pointer arrays and is one of the most confusing points in the concept of pointers in C. The key is to understand the unit of the “entire array”.
-
Pointer Array:
<span>int *arr[10]</span>-> An array containing 10 elements, each of which is an<span>int*</span>(priority:<span>[]</span>><span>*</span>). -
Array Pointer:
<span>int (*ptr)[10]</span>-> A pointer that points to an array containing 10<span>int</span>elements (using<span>()</span>changes the priority, so<span>ptr</span>binds with<span>*</span>first).
Code Example:
-
<span>matrix</span>is a two-dimensional array, which can be understood as “an array containing 3 elements, each of which is itself an array containing 4 integers”. -
<span>ptr</span>is a pointer. Its type is<span>int (*)[4]</span>, which means “a pointer to an array containing 4 integers”. -
<span>matrix</span>as an array name, in most expressions, will “decay” to point to its first element.<span>matrix</span>’s first element is its first row, which is an<span>int [4]</span>type array. Therefore, the decay of<span>matrix</span>results in a pointer to an<span>int [4]</span>array, which matches the type of<span>ptr</span>, allowing assignment.
2.2 Memory Layout and Working Principle – Stepping by Rows
Assuming the starting address of <span>matrix</span> is <span>0x1000</span>, and <span>int</span> occupies 4 bytes.
Now, ptr = matrix; means the address value stored in ptr is 0x1000.
The essence of pointer arithmetic:
-
<span>ptr + 1</span>: Here, the<span>1</span>unit is the size of the<span>object pointed to by ptr</span><code><span>. </span><code><span>ptr</span>points to an<span>int [4]</span>, which is an array containing 4 integers, with a size of<span>4 * sizeof(int) = 16</span><code><span> bytes. Therefore, </span><code><span>ptr + 1</span>will add 16 bytes to<span>0x1000</span>, resulting in the address<span>0x1010</span>, which is the starting address of the second row (<span>matrix[1]</span>). -
<span>*(ptr + 1)</span>: Dereferencing<span>ptr + 1</span>(the pointer to the second row) retrieves the second row array itself (<span>matrix[1]</span>). The array name in the expression will decay to point to its first element, so<span>*(ptr + 1)</span>is equivalent to<span>matrix[1]</span>, which is of type<span>int*</span>, pointing to<span>matrix[1][0]</span>(address<span>0x1010</span>). -
<span>*(ptr + 1) + 2</span>: The starting address of the second row (<span>0x1010</span>) plus<span>2 * sizeof(int) = 8</span>bytes results in the address<span>0x1018</span>, pointing to<span>matrix[1][2]</span>. -
<span>*(*(ptr + 1) + 2)</span>: Dereferencing the above address retrieves the value<span>matrix[1][2]</span>(<span>7</span>).
This is why we can use <span>ptr[i][j]</span> syntax that looks just like an array. The compiler will convert it to <span>*(*(ptr + i) + j)</span><code><span>.</span>
2.3 Advantages and Applicable Scenarios
-
Simplifying Multi-Dimensional Array Handling (Function Parameters):
-
Method A: Pass a Flattened Pointer and One-Dimensional Index
<span>(int *arr, int rows, int cols)</span> -
Method B: Pass a Pointer Array
<span>(int **arr, int rows, int cols)</span> -
Method C: Pass an Array Pointer
<span>(int (*arr)[4], int rows)</span>
-
Accessing elements within the function:
<span>arr[i * cols + j]</span> -
Disadvantages: The syntax is not intuitive and can easily lead to index calculation errors.
-
Disadvantages: This requires that the two-dimensional array itself must be dynamically allocated in the form of a pointer array, and cannot be used for a
<span>int matrix[3][4]</span>which is a real two-dimensional array. The memory layout is completely different.
-
Advantages: Inside the function, you can use the most natural
<span>arr[i][j]</span>syntax to access elements. It perfectly matches the memory model of statically defined two-dimensional arrays, making it the most type-safe and intuitive syntax for passing.
-
This is the primary and irreplaceable use of array pointers. In C, you cannot directly pass an entire array to a function by value. When you need to pass a two-dimensional array to a function, there are three common methods:
Improving Code Readability:
The function signature <span>void print_matrix(int (*matrix)[4], int rows)</span> clearly declares: “This function requires a two-dimensional array, where each row must contain 4 integers.” This itself serves as documentation.
Type Safety:
The compiler knows the length of each row. If you mistakenly try to access <span>matrix[i][5]</span>, the compiler may issue a warning. In contrast, in Method A, the compiler has no idea how many <span>cols</span> there are, and cannot perform any boundary checks.
2.4 Dynamic Multi-Dimensional Array Handling – The Powerful Combination with VLA

-
<span>int (*array)[cols]</span>: Declares a pointer<span>array</span>that points to an integer array of length<span>cols</span>. Here,<span>cols</span>is a variable, which is the VLA type. -
<span>sizeof(*array)</span>:<span>array</span>points to an<span>int [cols]</span>, so<span>*array</span>is an<span>int [cols]</span>type array. The result of<span>sizeof(*array)</span>is<span>cols * sizeof(int)</span>. This is calculated at runtime. -
<span>malloc(rows * sizeof(*array))</span>: Allocates a block of memory for<span>rows</span>rows, each of size<span>sizeof(*array)</span>. This memory layout is identical to that of a static<span>int rows[cols]</span>, being contiguous. -
The return value
<span>int (*)[]</span>is a pointer to an unspecified size array, and the caller needs to convert it to the correct type (like<span>int (*)[cols]</span>) for use.
<span>process_2d_array</span> function demonstrates how to directly use VLA array pointers in function parameters, allowing dynamic-sized two-dimensional arrays to have the same syntactic convenience as static arrays <span>array[i][j]</span>, making it a powerful tool for writing numerical computation and image processing library functions.
2.5 Common Pitfalls and Solutions
Pitfall 1: Confusing Pointer Types

-
Error Cause:
<span>matrix</span>decays to type<span>int (*)[4]</span>(a pointer to an array of 4 elements). Meanwhile,<span>wrong_ptr</span>has the type<span>int *</span>(a pointer to a single integer). -
Although their values are both
<span>0x1000</span>, their types are completely different. -
<span>wrong_ptr + 1</span>: Based on the size of<span>int</span>, it steps forward, resulting in<span>0x1000 + 4 = 0x1004</span>(pointing to<span>matrix[0][1]</span>). -
<span>correct_ptr + 1</span>: Based on the size of<span>int [4]</span>, it steps forward, resulting in<span>0x1000 + 16 = 0x1010</span>(pointing to<span>matrix[1][0]</span>). -
If you try to simulate two-dimensional array access through
<span>wrong_ptr</span>, the calculations will be completely incorrect, and the warnings given by the compiler are crucial and should never be ignored.
Pitfall 2: Array Dimension Mismatch

-
Error Cause: This is a strict type mismatch error.
<span>int (*)[4]</span>and<span>int (*)[5]</span>are two completely different pointer types, just like<span>int*</span>and<span>float*</span>are different. -
Compiler’s Role: The compiler reports an error to prevent catastrophic mistakes. If such an assignment were allowed, then
<span>ptr + 1</span>would advance by<span>5 * sizeof(int) = 20</span>bytes, while in reality, the next row is only 16 bytes apart. Any access through<span>ptr</span>would be misaligned, leading to data read/write confusion. -
Solution: You must ensure that the dimensions of the array pointer match exactly with the dimensions of the actual array it points to. This is fundamental to ensuring code correctness.
3. Pointer Arrays vs Array Pointers: A Comprehensive Comparison
3.1 Declaration Syntax Comparison

3.2 Memory Layout Comparison
Memory layout of pointer arrays:
arr1[0] -> address1
arr1[1] -> address2
...
arr1[9] -> address10
Memory layout of array pointers:
arr2 -> [starting address of the entire array]
3.3 Pointer Arithmetic Comparison

3.4 Application Scenario Comparison

4. Key Differences Mnemonic
A pointer array is an array where all elements are pointers; an array pointer is a pointer that points to an entire array.
Look at the declaration:<span>int *arr[10]</span> – first see <span>[]</span>, so it is an array; <span>int (*arr)[10]</span> – first see <span>*</span>, so it is a pointer.
5. Selection Guide
-
If you need to manage a collection of data of different lengths → choose pointer arrays
-
If you need to handle multi-dimensional arrays while maintaining structure → choose array pointers
-
If you need a collection of data that can grow dynamically → choose pointer arrays
-
If you need to perform matrix operations → choose array pointers
6. Best Practices
-
Always initialize pointers: Avoid wild pointers
-
Use const to protect data: Prevent accidental modifications
-
Check for memory allocation failures: malloc may return NULL
-
Release memory in a timely manner: Avoid memory leaks
-
Use static arrays: When you need to return an array pointer
These personal views are for reference only; any issues are welcome for criticism and correction.