Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
Dynamic Rows with Pointer Arrays
Lecture 51 of the free C programming course: use an array of pointers in C to build a 2D table where the number of rows is fixed and the number of columns is chosen at run time.
In the previous lecture the column count was fixed. Real programs are not always that kind. Imagine three temperature sensors, and the user decides how many readings to keep from each one. The number of rows (sensors) never changes, but the number of columns (readings) does. The natural tool is an array of pointers in C: a small fixed array in which every element points to its own dynamically allocated row.
This lecture belongs to the free embedded C course on EmbeddedPathashala. It is a practical free C tutorial step for anyone learning C programming for embedded systems, where per-channel buffers of different lengths are very common.
What You Will Learn
- What an array of pointers in C is and how it differs from a pointer to an array
- How to allocate one row per pointer with
callocand fill a 2D table - Why the rows may sit in different places in memory
- How to clean up safely, even when an allocation fails halfway
- How to resize one row independently with
realloc
Prerequisites
You should have finished the previous lecture (cp8_50) and know malloc, calloc and free. A reminder of arrays of pointers from earlier in the Pointers chapter will also help.
Array of Pointers in C: The Declaration
Here is the declaration we will use:
int *row[3];
Because [] binds tighter than *, row is first of all an array of 3 elements, and each element is a pointer to int. So row itself holds three addresses, and each address can point to the first element of an int array of any length. The array of pointers takes the same small space regardless of how long the rows are: on a 64-bit machine that is 3 × 8 = 24 bytes.
| Feature | Pointer to array int (*p)[4] | Array of pointers int *p[3] |
|---|---|---|
| Fixed dimension | Number of columns | Number of rows |
| Run-time dimension | Number of rows | Number of columns |
| Allocation calls | One malloc | One per row |
| Rows contiguous? | Always | Not guaranteed |
| Cleanup | One free | One free per row |
| Element access | p[r][c] | p[r][c] |
Notice the last line: the syntax for using the table is identical. Only the way it is built and the way it is released differ.
Building the Table Row by Row
The plan has three parts. First, set every pointer to NULL when you declare the array. Second, allocate one row per pointer. Third, if any allocation fails, free whatever was already obtained. The NULL initialisation makes that last step trivial, because calling free(NULL) is defined to do nothing.
Three pointers, three separate row blocks
Each block comes from its own calloc call. The allocator may place block B right after block A, or far away from it. Never assume the rows are neighbours, and never walk from the end of one row into the next.
Complete Example: Per-Sensor Reading Buffers
Save the program below as sensor_rows.c.
#include <stdio.h>
#include <stdlib.h>
#define SENSORS 3
int main(void)
{
size_t readings;
int *row[SENSORS] = { NULL }; /* NULL so free() is always safe */
printf("Readings per sensor? ");
if (scanf("%zu", &readings) != 1 || readings == 0) {
fprintf(stderr, "invalid count\n");
return 1;
}
for (size_t s = 0; s < SENSORS; s++) {
row[s] = calloc(readings, sizeof *row[s]);
if (row[s] == NULL) {
perror("calloc");
for (size_t k = 0; k < SENSORS; k++)
free(row[k]); /* frees what we got, ignores NULLs */
return 1;
}
}
for (size_t s = 0; s < SENSORS; s++)
for (size_t i = 0; i < readings; i++)
row[s][i] = (int)((s + 1) * 100 + i);
for (size_t s = 0; s < SENSORS; s++) {
printf("sensor %zu:", s);
for (size_t i = 0; i < readings; i++)
printf(" %d", row[s][i]);
putchar('\n');
}
printf("sizeof row (the pointer table) = %zu bytes\n", sizeof row);
printf("sizeof *row[0] (one int) = %zu bytes\n", sizeof *row[0]);
for (size_t s = 0; s < SENSORS; s++)
free(row[s]);
return 0;
}
Build and Run
$ gcc -std=c11 -Wall -Wextra -Wpedantic -o sensor_rows sensor_rows.c
$ ./sensor_rows
Readings per sensor? 4
sensor 0: 100 101 102 103
sensor 1: 200 201 202 203
sensor 2: 300 301 302 303
sizeof row (the pointer table) = 24 bytes
sizeof *row[0] (one int) = 4 bytes
The pointer table is 24 bytes on a typical 64-bit Linux machine and would be 12 bytes on a 32-bit microcontroller. The actual data lives in the three separately allocated blocks.
Understanding the Key Lines
int *row[SENSORS] = { NULL };sets the first element to NULL, and C zero-initialises the rest, so all three are NULL.calloc(readings, sizeof *row[s])reservesreadingsints and zero-fills them. It also checks that the multiplication does not overflow.row[s][i]works becauserow[s]is anint *, and subscripting a pointer is the same as pointer arithmetic.- The final loop frees one block per row. There is no block for
rowitself, because the array lives on the stack.
Rows of Different Lengths
An array of pointers has a bonus: each row may have a different length, because each one is independent. A ragged table, for example, could store 10 readings for sensor 0 and 500 for sensor 1. The only extra work is remembering each length, usually in a parallel array such as size_t len[SENSORS]. You can also resize a single row later:
int *tmp = realloc(row[1], new_len * sizeof *tmp);
if (tmp != NULL)
row[1] = tmp; /* only replace on success */
Always assign the result of realloc to a temporary first. If it fails it returns NULL, but your original block is still valid and must still be freed.
When to Choose an Array of Pointers in C
Choosing between the two methods you have now seen is mostly about which dimension stays constant. Use an array of pointers in C when the number of rows is a small, known constant, such as three sensors, four UART ports or eight priority levels, while the depth of each row depends on configuration or input. Use the pointer-to-array method of cp8_50 when every row has the same width and you want one contiguous block.
Think about the memory cost with a quick example. Three sensors with 1000 readings each need 3 × 1000 × 4 = 12000 bytes of data. The pointer table adds only 24 bytes on a 64-bit machine, but the allocator also keeps a little bookkeeping information for each of the three blocks. For three rows that overhead is irrelevant. For thousands of tiny rows it becomes significant, and that is the moment to switch to a single flat block.
One more practical point: because every row has its own pointer, you can hand a single row to another function as a plain int * plus a length. That makes it easy to write one routine, say average(int *samples, size_t n), and call it for each sensor with average(row[s], readings). This reuse is a big reason an array of pointers in C is so common in embedded firmware.
Real-World Uses in Embedded C Programming
- Per-channel logging: every channel keeps a history of its own depth.
- Command tables: a fixed list of devices, each with a buffer sized for its protocol.
- Packet queues: a fixed number of priority levels, each with a variable queue length.
Common Mistakes with an Array of Pointers in C
| Mistake | Result | Fix |
|---|---|---|
| Forgetting to allocate a row | Writing through an uninitialised pointer | Initialise with NULL and check each calloc |
| Treating rows as contiguous | Reading past the end of a block | Index each row separately |
Freeing only row[0] | Memory leak of the other rows | Loop over every row |
Calling free on row | Undefined behaviour, it is not heap memory | Free only the row blocks |
| Overwriting the pointer with a failed realloc | Lost block and a leak | Use a temporary pointer |
Warning: if the program returns early without freeing the rows, you leak memory. On a long-running embedded Linux service, small leaks accumulate until the process is killed by the system. Keep one cleanup path.
Best Practices, Performance and Security
- Performance: walking within one row is cache friendly, but jumping between rows can be slower than in a contiguous block, and every row costs its own allocator overhead.
- Fragmentation: many small allocations fragment the heap. Prefer the single-block method of cp8_50 when all rows have equal length.
- Security: validate the user-supplied count before allocating, and prefer
callocfor its overflow check. - Style: use
sizeof *row[s]rather thansizeof(int), so the code stays correct if you change the element type. - Testing: run once with AddressSanitizer enabled to catch leaks and overruns.
Summary and Key Takeaways
- An array of pointers,
int *row[N], gives a fixed number of rows with run-time length. - Each row is allocated and freed on its own.
- Initialise the pointers to NULL so cleanup is safe at any point.
- Rows are not guaranteed to be contiguous, and they can differ in length.
- Subscript syntax
row[r][c]is the same as for any 2D array.
You can now build a table with a fixed row count and any column count. The next lecture removes both limits by using a pointer to a pointer, so rows and columns are both chosen at run time. Keep going with this free C programming course to finish the Pointers chapter.
Frequently Asked Questions
What is an array of pointers in C?
It is an array whose elements are pointers, for example int *row[3]. Each pointer can refer to a separate block of data.
How is it different from a pointer to an array?
A pointer to an array is a single pointer that steps over whole rows of a fixed width. An array of pointers holds many pointers, one per row, and each row can have any length.
Why initialise the pointer array with NULL?
So that free can be called on every element during cleanup without checking which allocations succeeded, since freeing NULL does nothing.
Are the rows stored next to each other in memory?
Not necessarily. Every row comes from a separate allocation, so the allocator decides where each one goes.
Can each row have a different number of elements?
Yes. Keep the lengths in a separate array and use realloc to resize an individual row.
Do I free the pointer array itself?
Only if the pointer array was also allocated with malloc. In this lecture it is an ordinary local array, so only the rows are freed.
Continue the Free C Programming Course
Master embedded C programming step by step with free lectures, runnable examples, quizzes and interview questions from EmbeddedPathashala.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments