Definition
An array of pointers is a contiguous collection where each element stores a memory address pointing to a variable, dynamically allocated block, string, or another array. Unlike standard arrays that store data values directly, arrays of pointers store references, enabling flexible memory layouts, jagged data structures, and efficient handling of variable-sized objects.
Declaration & Operator Precedence
type *array_name[size];
| Syntax | Meaning | Precedence Rule |
|---|---|---|
int *ptrs[5]; | Array of 5 pointers to int | [] binds tighter than * |
int (*ptr)[5]; | Pointer to an array of 5 int | Parentheses override precedence |
const char *days[7]; | Array of 7 pointers to read-only strings | Common for string tables |
Memory Layout & Access Behavior
- Array Storage: The pointer array itself occupies
size * sizeof(type *)contiguous bytes. - Pointed Data: Objects referenced by each pointer can reside anywhere in memory (stack, heap, static data, or different translation units).
- Access Pattern:
arr[i]→ yields the pointer at indexi*arr[i]orarr[i][0]→ dereferences the pointer to access the first elementarr[i][j]→ accesses elementjof the object pointed to byarr[i]
Common Use Cases & Examples
1. Array of Strings (Read-Only Table)
#include <stdio.h>
int main(void) {
const char *colors[] = {"Red", "Green", "Blue", NULL};
for (int i = 0; colors[i] != NULL; i++) {
printf("%s\n", colors[i]);
}
return 0;
}
2. Dynamic Jagged (Ragged) 2D Array
#include <stdio.h>
#include <stdlib.h>
int main(void) {
size_t rows = 3;
size_t cols[] = {2, 4, 3}; // Each row has different length
// 1. Allocate array of pointers
int **matrix = malloc(rows * sizeof(int *));
// 2. Allocate each row individually
for (size_t i = 0; i < rows; i++) {
matrix[i] = malloc(cols[i] * sizeof(int));
for (size_t j = 0; j < cols[i]; j++) {
matrix[i][j] = i * 10 + j;
}
}
// 3. Use & free
printf("%d\n", matrix[1][2]); // Valid: row 1 has 4 elements
for (size_t i = 0; i < rows; i++) free(matrix[i]);
free(matrix);
return 0;
}
Rules & Constraints
- Type Consistency: All pointers in the array must point to the same base type. Mixing
int *andfloat *in one array violates type safety. - Two-Phase Memory Management: For dynamically allocated pointer arrays, you must:
- Allocate/free each pointed-to object individually
- Allocate/free the pointer array itself
- No Implicit Initialization: Uninitialized pointer elements contain garbage addresses. Dereferencing them invokes undefined behavior.
- Decay Behavior: When passed to functions, the array decays to a pointer to its first element (
type **). Size information is lost and must be passed separately. - Alignment: Each pointer must be properly aligned for its target type. Misaligned pointers cause hardware faults on strict architectures (ARM, RISC-V).
Best Practices
- Initialize to
NULL:int *arr[10] = {0};prevents accidental dereference of garbage addresses. - Track sizes explicitly: Store row lengths in a parallel array or struct when using jagged layouts.
- Free in reverse order: Deallocate pointed-to memory before freeing the pointer array to avoid leaks.
- Use
constfor string tables:const char *names[]prevents accidental modification and places literals in read-only memory. - Prefer structs over parallel arrays: Group pointer, size, and metadata together to reduce coupling.
- Validate before dereference: Always check
if (arr[i] != NULL)before accessing*arr[i].
Common Pitfalls
- 🔴 Memory leaks: Freeing only the pointer array while leaving dynamically allocated rows in memory.
- 🔴 Dangling pointers: Freeing a pointed-to object without setting the array element to
NULL→ use-after-free on subsequent access. - 🔴 Confusing
char *arr[]withchar arr[][N]: The former stores pointers to scattered strings; the latter stores a contiguous 2D character grid. Memory layout andsizeof()differ drastically. - 🔴 Dereferencing uninitialized elements:
int *p[3]; p[0] = malloc(...); *p[1] = 5;→p[1]is garbage → segmentation fault. - 🔴 Assuming contiguous data: Pointer arrays do not guarantee that pointed-to objects are adjacent. Pointer arithmetic across rows is undefined.
- 🔴 Ignoring
NULLtermination: Looping without bounds or sentinel checks reads past allocated pointers → undefined behavior.
Standards & Tooling
- C Standard: Fully supported since C89. Semantics unchanged in C99, C11, C17, and C23.
- Compiler Warnings:
-Wuninitialized,-Wmissing-field-initializers, and-Wnull-dereferencecatch unsafe pointer array usage. - Static Analysis:
clang-tidy,cppcheck, andCoveritydetect memory leaks, double frees, and uninitialized pointer accesses in arrays. - Runtime Validation: AddressSanitizer (
-fsanitize=address) and Valgrind precisely track allocation/deallocation order and detect use-after-free or out-of-bounds pointer array access. - Debugging: GDB/LLDB display pointer arrays as
[0x..., 0x..., ...]. Useprint *arr[i]orx/4xw arrto inspect pointed data. - Modern Alternatives: For complex dynamic layouts, consider
structwrappers with embedded size/metadata, or switch to C++std::vectorif ABI flexibility allows.
Arrays of pointers are a cornerstone of flexible C programming. Mastering their declaration, memory layout, and lifecycle management enables efficient string handling, dynamic multidimensional data, and clean generic APIs while avoiding common memory safety hazards.
C Preprocessor, Macros & Compilation Directives (Complete Guide)
https://macronepal.com/aws/mastering-c-variadic-macros-for-flexible-debugging/
Explains variadic macros in C, allowing functions/macros to accept a variable number of arguments for flexible logging and debugging.
https://macronepal.com/aws/mastering-the-stdc-macro-in-c/
Explains the __STDC__ macro, which indicates compliance with the C standard and helps ensure portability across compilers.
https://macronepal.com/aws/c-time-macro-mechanics-and-usage/
Explains the __TIME__ macro, which provides the compilation time of a program and is often used for logging and debugging.
https://macronepal.com/aws/understanding-the-c-date-macro/
Explains the __DATE__ macro, which inserts the compilation date into programs for tracking builds.
https://macronepal.com/aws/c-file-type/
Explains the __FILE__ macro, which represents the current file name during compilation and is useful for debugging.
https://macronepal.com/aws/mastering-c-line-macro-for-debugging-and-diagnostics/
Explains the __LINE__ macro, which provides the current line number in source code, helping in error tracing and diagnostics.
https://macronepal.com/aws/mastering-predefined-macros-in-c/
Explains all predefined macros in C, including their usage in debugging, portability, and compile-time information.
https://macronepal.com/aws/c-error-directive-mechanics-and-usage/
Explains the #error directive in C, used to generate compile-time errors intentionally for validation and debugging.
https://macronepal.com/aws/understanding-the-c-pragma-directive/
Explains the #pragma directive, which provides compiler-specific instructions for optimization and behavior control.
https://macronepal.com/aws/c-include-directive/
Explains the #include directive in C, used to include header files and enable code reuse and modular programming.
HTML Online Compiler
https://macronepal.com/free-html-online-code-compiler/
Python Online Compiler
https://macronepal.com/free-online-python-code-compiler/
Java Online Compiler
https://macronepal.com/free-online-java-code-compiler/
C Online Compiler
https://macronepal.com/free-online-c-code-compiler/
C Online Compiler (Version 2)
https://macronepal.com/free-online-c-code-compiler-2/
Node.js Online Compiler
https://macronepal.com/free-online-node-js-code-compiler/
JavaScript Online Compiler
https://macronepal.com/free-online-javascript-code-compiler/
Groovy Online Compiler
https://macronepal.com/free-online-groovy-code-compiler/
J Shell Online Compiler
https://macronepal.com/free-online-j-shell-code-compiler/
Haskell Online Compiler
https://macronepal.com/free-online-haskell-code-compiler/
Tcl Online Compiler
https://macronepal.com/free-online-tcl-code-compiler/
Lua Online Compiler
https://macronepal.com/free-online-lua-code-compiler/