What are Dynamic Rows with Pointer Arrays- C Programming Course For Engineering Students

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

PREV_LEC | NEXT_LEC

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 calloc and 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.

FeaturePointer to array int (*p)[4]Array of pointers int *p[3]
Fixed dimensionNumber of columnsNumber of rows
Run-time dimensionNumber of rowsNumber of columns
Allocation callsOne mallocOne per row
Rows contiguous?AlwaysNot guaranteed
CleanupOne freeOne free per row
Element accessp[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

row[0] → block A: 100101102103
row[1] → block B: 200201202203
row[2] → block C: 300301302303

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]) reserves readings ints and zero-fills them. It also checks that the multiplication does not overflow.
  • row[s][i] works because row[s] is an int *, and subscripting a pointer is the same as pointer arithmetic.
  • The final loop frees one block per row. There is no block for row itself, 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

MistakeResultFix
Forgetting to allocate a rowWriting through an uninitialised pointerInitialise with NULL and check each calloc
Treating rows as contiguousReading past the end of a blockIndex each row separately
Freeing only row[0]Memory leak of the other rowsLoop over every row
Calling free on rowUndefined behaviour, it is not heap memoryFree only the row blocks
Overwriting the pointer with a failed reallocLost block and a leakUse 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 calloc for its overflow check.
  • Style: use sizeof *row[s] rather than sizeof(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.

PREV_LEC | NEXT_LEC

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *