What is Pointer to Pointer 2D Array- C Programming Training Institute In Hyderabad

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

Pointer to Pointer 2D Array

Lecture 52 of the free C programming course: allocate a fully dynamic pointer to pointer 2D array in C, where both rows and columns are decided at run time, and learn to free and resize it safely.

The last two lectures each had one fixed dimension. Now we remove both limits. A pointer to pointer 2D array in C, declared as int **g, lets the user choose the number of rows and the number of columns while the program runs. This is the most flexible way to build a dynamic 2D array in C, and it is also where most memory leaks are born, so we will build it carefully with helper functions.

This lecture of the free embedded C course keeps the style of the earlier ones: small original code, tested output, and habits that make embedded C programming safer.

What You Will Learn

  • How int **g works as a table of row pointers
  • How to write grid_new and grid_free helpers with correct rollback on failure
  • The right order for freeing: rows first, then the table
  • How to grow a single row with realloc without breaking the rest
  • When a single flat block is a better choice

Prerequisites

You need cp8_50 and cp8_51 from this free C tutorial, plus the earlier lectures on pointer to pointer (double pointers) and on malloc, calloc, realloc and free.

How a Pointer to Pointer 2D Array Works

The idea is two levels of allocation. Level one is a block that holds rows pointers, each of type int *. The address of that block is stored in g, whose type is int **, a pointer to an int *. Level two is one block of cols integers per row, and each of those addresses is stored in the level-one block.

Two levels: pointer table and row blocks

g → table of 3 pointers
g[0] → 1234
g[1] → 2468
g[2] → 36912

The expression g[r][c] is evaluated in two steps. g[r] reads the pointer stored in slot r of the table, and [c] then indexes into the row it points to. Unlike cp8_50, there is no multiplication of row length; there is a memory load instead.

MethodFixed sizeAllocationsContiguousBest when
Pointer to array (cp8_50)Columns1YesColumn count is constant
Array of pointers (cp8_51)RowsrowsNoFew rows, variable lengths
Pointer to pointer (this lecture)Nonerows + 1NoBoth sizes unknown, ragged rows

Writing Safe Helper Functions

With rows + 1 allocations, error handling is the hard part. If row 5 fails, rows 0 to 4 and the table must be released before you return. Putting that logic into two small functions keeps main clean and the rules in one place.

Complete Example: Multiplication Table Grid

Save this program as grid_demo.c. It builds a table, fills it with a multiplication table, prints it, then grows row 1 by two elements.

#include <stdio.h>
#include <stdlib.h>

static int **grid_new(size_t rows, size_t cols)
{
    int **g = malloc(rows * sizeof *g);
    if (g == NULL)
        return NULL;

    for (size_t r = 0; r < rows; r++) {
        g[r] = calloc(cols, sizeof **g);
        if (g[r] == NULL) {
            while (r-- > 0)         /* undo the rows already built */
                free(g[r]);
            free(g);
            return NULL;
        }
    }
    return g;
}

static void grid_free(int **g, size_t rows)
{
    if (g == NULL)
        return;
    for (size_t r = 0; r < rows; r++)
        free(g[r]);                 /* rows first ... */
    free(g);                        /* ... then the table */
}

static void grid_print(int **g, size_t rows, size_t cols)
{
    for (size_t r = 0; r < rows; r++) {
        for (size_t c = 0; c < cols; c++)
            printf("%4d", g[r][c]);
        putchar('\n');
    }
}

int main(void)
{
    size_t rows, cols;

    printf("rows and columns? ");
    if (scanf("%zu %zu", &rows, &cols) != 2 || rows == 0 || cols == 0) {
        fprintf(stderr, "invalid size\n");
        return 1;
    }

    int **g = grid_new(rows, cols);
    if (g == NULL) {
        fprintf(stderr, "out of memory\n");
        return 1;
    }

    for (size_t r = 0; r < rows; r++)
        for (size_t c = 0; c < cols; c++)
            g[r][c] = (int)((r + 1) * (c + 1));      /* multiplication table */

    puts("table:");
    grid_print(g, rows, cols);

    /* grow row 1 by two more elements without touching other rows */
    if (rows > 1) {
        size_t newcols = cols + 2;
        int *tmp = realloc(g[1], newcols * sizeof *tmp);
        if (tmp == NULL) {
            fprintf(stderr, "realloc failed, row 1 unchanged\n");
        } else {
            g[1] = tmp;
            g[1][cols]     = -1;
            g[1][cols + 1] = -1;
            printf("row 1 after growing to %zu columns:", newcols);
            for (size_t c = 0; c < newcols; c++)
                printf(" %d", g[1][c]);
            putchar('\n');
        }
    }

    grid_free(g, rows);
    return 0;
}

Build and Run

$ gcc -std=c11 -Wall -Wextra -Wpedantic -o grid_demo grid_demo.c
$ ./grid_demo
rows and columns? 3 4
table:
   1   2   3   4
   2   4   6   8
   3   6   9  12
row 1 after growing to 6 columns: 2 4 6 8 -1 -1

Walking Through the Tricky Parts

  • Table allocation: malloc(rows * sizeof *g). Here *g has type int *, so the size is one pointer per row.
  • Row allocation: calloc(cols, sizeof **g). Here **g is a single int.
  • Rollback loop: while (r-- > 0) compares first, then decrements, so it visits r-1 down to 0 and never touches the failed row.
  • Free order: if you free the table first, you lose the only copies of the row addresses and leak every row.
  • Growing a row: only g[1] changes. The other rows still hold cols elements, so after this step the grid is ragged and you must remember that row 1 is longer.

Correct free order

1 → free(g[0])free(g[1])free(g[2])
2 → free(g)
wrong → free(g) first = rows leaked

An Alternative: One Flat Block

If both dimensions are known before allocating and rows never change length, a single block is usually better than a pointer to pointer 2D array:

int *flat = calloc(rows * cols, sizeof *flat);   /* check for NULL */
/* element (r, c) lives at flat[r * cols + c] */
free(flat);

It needs one allocation and one free, it is contiguous and cache friendly, and it can be passed straight to functions that expect a buffer, such as DMA or file I/O. The price is writing the index arithmetic yourself. Many embedded C programming teams wrap it in a tiny macro or inline function.

Sizing a Pointer to Pointer 2D Array

Before allocating, estimate what the structure will cost. For a 3 × 4 table of 4-byte integers on a 64-bit system, the data is 48 bytes, the table of row pointers is 3 × 8 = 24 bytes, and the allocator adds bookkeeping for four separate blocks (three rows and the table). A flat block would need only 48 bytes plus one block of bookkeeping. On a PC that difference is invisible; on a board with 64 KB of RAM and a thousand rows it is not.

Also think about who owns the row lengths. In the demo program every row started with the same width, so a single cols variable was enough. After you resize one row, a pointer to pointer 2D array has become ragged, and a single width no longer describes it. The usual fix is a small struct that keeps the table, the number of rows and an array of lengths together, so the information can never get out of step with the memory it describes.

Finally, remember that a pointer to pointer 2D array can be given to a function as int **g together with the sizes, which is convenient. That is exactly why this layout is common in code that reads matrices, parses text tables or builds lookup structures at start-up, before the real-time part of the firmware begins.

Common Mistakes with a Pointer to Pointer 2D Array

MistakeResultFix
Freeing the table before the rowsLeak of every rowFree rows, then the table
Using sizeof(int) for the tableTable too small on 64-bit systemsUse sizeof *g
No rollback on partial failureLeak when memory is lowKeep the cleanup inside the helper
Passing int ** to a function expecting int [][4]Compiler warning and wrong readsThey are different types
Forgetting row lengths after reallocOut-of-bounds accessStore a length per row

Warning: an int ** is not the same thing as a real 2D array. int a[3][4] cannot be passed where int ** is expected, because the array has no table of pointers behind it. Mixing them compiles with a warning at best and crashes at worst.

Best Practices, Performance and Security

  • Performance: every access needs an extra load for the row pointer, and the rows may be scattered. For heavy numeric loops prefer the flat block.
  • Security: sanitise rows and cols from the user and keep an upper limit. A huge value is an easy denial-of-service on a small device.
  • Ownership: decide which function owns the memory. Allocate and free with a matching pair of functions, as grid_new and grid_free do.
  • Dangling pointers: after grid_free, set your pointer to NULL.
  • Verification: compile once with -fsanitize=address,undefined to prove there are no leaks or overruns.

Summary and Key Takeaways

  • int **g points to a table of row pointers, and each pointer owns a row block.
  • Allocation takes rows + 1 calls, so wrap it in helper functions with rollback.
  • Free every row first and the table last.
  • Rows can be resized or have different lengths, but you must track each length.
  • A flat block with r * cols + c is often simpler and faster.

You have now seen all three ways to create a dynamic 2D array in C. Next, we move to a new topic: functions also live in memory and have addresses. That is the start of function pointers, a core feature of embedded C programming. Continue this free C programming course.

Frequently Asked Questions

What does int **g mean in C?

It is a pointer to a pointer to int. In a dynamic 2D array it points to the table that stores one int pointer per row.

Why must rows be freed before the table?

The table is the only place that stores the row addresses. If you free it first, you can no longer reach the rows and they are leaked.

Is a pointer to pointer the same as a 2D array?

No. A real 2D array is one contiguous block with a fixed row width. A pointer to pointer is a table of separate row pointers.

How do I resize one row?

Call realloc on that row’s pointer using a temporary variable, and replace the row pointer only when realloc succeeds. Remember to store the new length.

When should I use a flat array instead?

When all rows have the same length and you want fewer allocations, better cache behaviour, or a buffer you can pass to hardware or file functions.

Can I use this approach on a microcontroller?

Yes if a heap exists and fragmentation is acceptable. Many bare-metal projects avoid many small allocations and use a fixed pool instead.

Learn Embedded C Programming for Free

Follow the full free embedded C course on EmbeddedPathashala: pointers, memory, functions, quizzes and interview preparation in one place.

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 *