What are Passing 2D Arrays in C- Embedded C Programming Training In Hyderabad

Need Help or More Information?

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

Free C Programming Course · Chapter 8 · Pointers

Passing 2D Arrays in C

Learn how to pass a 2D array to a function in C, why the column count is mandatory, and how a matrix parameter becomes a pointer to a row. A practical lesson from our free C tutorial for embedded C programming.

3
Equivalent ways to write the parameter
1
Dimension you may leave out
20
Byte row stride in our demo

What You Will Learn

Two-dimensional data is everywhere in embedded systems: a keypad scan matrix, an LED panel frame, a thermal camera image, a lookup table of calibration values. This lecture of our free C programming course explains how to pass a 2D array to a function in C correctly, what the function actually receives, and why the compiler insists that you spell out every dimension except the first.

  • The two classic ways of writing a matrix parameter, and the pointer-to-array form they both turn into.
  • Why the receiving function gets a pointer to a row, not a pointer to an individual element.
  • How the row length controls pointer arithmetic, with measured proof from a running program.
  • A modern C99 alternative for matrices whose width is only known at run time.
  • Why a flat array is often the preferred design in firmware.

Prerequisites

Review the two earlier lectures in this chapter on passing one-dimensional arrays and on sizeof with array parameters, plus the chapter on declaring and initialising 2-D arrays. You should know that C stores a 2-D array row after row in memory.

How to Pass a 2D Array to a Function in C

Suppose a sensor board has a small thermal grid with three rows and five columns of temperature readings, declared as int thermal[3][5]. To process it in a helper function, you write the parameter with the column count included:

Parameter spellingMeaning
int g[3][5]Array of 3 rows; the 3 is ignored
int g[][5]Array of unspecified rows, each with 5 ints
int (*g)[5]Pointer to an array of 5 ints

All three are the same type once the compiler is done: a pointer to an array of five integers. The first two forms are just friendlier ways of writing the third. This mirrors what we saw for one dimension, with one extension: the pointed-to thing is now a whole row instead of a single element.

What the Callee Receives

g points hererow 0 : 21 22 23 22 21
g + 1row 1 : 24 25 26 25 24
g + 2row 2 : 27 28 29 28 27
Each step of g jumps one whole row: 5 ints × 4 bytes = 20 bytes

The Parameter Is a Pointer to a Row

Because the function has only one pointer, sizeof(g) inside the function is the pointer size and says nothing about the matrix. But sizeof(*g) is meaningful: dereferencing the pointer gives you one complete row, whose size the compiler knows because the column count is part of the type. That is the whole reason the column count must be written. Without it, the compiler would not know how many bytes to skip to reach the next row.

Why the Column Count Is Required

Recall how element g[r][c] is found. The compiler computes the address of row r by adding r row-sizes to the base, and then adds c element-sizes. The row size is the column count times the element size. If you leave the column count out, as in int g[][], the row size is unknown and the compiler rejects the declaration. The rows count, in contrast, is never needed for the address computation. That asymmetry is why only the leftmost dimension may be omitted.

Caution: passing a 3 x 5 matrix to a function declared with int (*g)[4] draws an incompatible-pointer warning (a hard error in newer compilers), and if forced through it reads every row from the wrong offset. The column count in the callee must match the real array exactly.

Build and Run: Thermal Grid Demo

This original demo shifts every reading in a 3 x 5 grid by a constant and prints the numbers that prove the pointer-to-row story. Save it as ep_grid_pass.c.

#include <stdio.h>
#include <stddef.h>

#define COLS 5

/* Three spellings, one meaning: pointer to an array of COLS ints. */
void shift_grid(int g[3][COLS], size_t rows, int delta);
void shift_grid(int g[][COLS], size_t rows, int delta);
void shift_grid(int (*g)[COLS], size_t rows, int delta);

void shift_grid(int (*g)[COLS], size_t rows, int delta)
{
    printf("inside : sizeof(g)  = %zu (the pointer)\n", sizeof(g));
    printf("inside : sizeof(*g) = %zu (one whole row)\n", sizeof(*g));
    printf("inside : g+1 is %td bytes after g\n",
           (char *)(g + 1) - (char *)g);

    for (size_t r = 0; r < rows; r++) {
        for (size_t c = 0; c < COLS; c++) {
            g[r][c] += delta;
        }
    }
}

/* C99: let the caller choose the width at run time. */
static void print_grid(size_t rows, size_t cols, int g[rows][cols])
{
    for (size_t r = 0; r < rows; r++) {
        for (size_t c = 0; c < cols; c++) {
            printf("%3d", g[r][c]);
        }
        printf("\n");
    }
}

int main(void)
{
    int thermal[3][COLS] = {
        {21, 22, 23, 22, 21},
        {24, 25, 26, 25, 24},
        {27, 28, 29, 28, 27},
    };

    printf("main   : sizeof(thermal) = %zu\n", sizeof(thermal));
    shift_grid(thermal, 3, 2);
    printf("grid after shift_grid(+2):\n");
    print_grid(3, COLS, thermal);
    return 0;
}
gcc -std=c11 -Wall -Wextra -o ep_grid_pass ep_grid_pass.c
./ep_grid_pass

Expected output on a 64-bit Linux machine (integers are 4 bytes):

main   : sizeof(thermal) = 60
inside : sizeof(g)  = 8 (the pointer)
inside : sizeof(*g) = 20 (one whole row)
inside : g+1 is 20 bytes after g
grid after shift_grid(+2):
 23 24 25 24 23
 26 27 28 27 26
 29 30 31 30 29

Reading the Numbers

  • 60 is 3 rows × 5 columns × 4 bytes, the whole matrix, visible only in main().
  • 8 is just a pointer on this machine (it would be 4 on a Cortex-M).
  • 20 is one row, and the pointer step g + 1 moves exactly that far. That is pointer arithmetic on a pointer-to-array.
  • Every element rose by 2 in the caller’s grid, proving once more that no copy was made.

When you compile with -Wall, recent GCC prints -Warray-parameter notes because the three prototypes use different spellings. That is expected in this teaching demo, and a good reason to standardise on one spelling in real code.

Variable Width With C99 Parameters

The helper print_grid() in the demo uses a C99 feature: the dimensions appear as earlier parameters and are then used in the matrix declaration, int g[rows][cols]. The sizes must come first in the parameter list. This is still a pointer to a row at the machine level, but the row length is now a run-time value, so one function can handle grids of different widths.

Use this for host-side tools and test code. On resource-constrained microcontrollers, check your coding standard first: many safety-oriented standards restrict variable-length arrays, and some embedded compilers support them only partly.

The Flat Array Alternative

Many firmware teams avoid 2-D parameters altogether. They store the grid in a one-dimensional array of rows * cols elements and compute the index as r * cols + c. The function then takes a plain int * plus the two sizes, which avoids the column-count restriction and works for any shape. The memory layout is identical to a true 2-D array, since C stores matrices row by row anyway.

ApproachStrengthWeakness
Pointer to row int (*g)[COLS]Natural g[r][c] syntaxWidth fixed at compile time
C99 int g[rows][cols]Natural syntax and flexible widthVLA rules vary across coding standards
Flat int *buf with sizesWorks anywhere, any shapeYou compute r * cols + c yourself

Real-World Use Cases

  • Keypad scanning: a 4 x 3 key map passed to a decode routine.
  • LED matrix drivers: a frame buffer of rows and columns handed to a draw function.
  • Calibration tables: two-dimensional lookup tables indexed by temperature and voltage.
  • Image processing: small thermal or infrared sensor frames filtered row by row.

Common Mistakes and Troubleshooting

MistakeResultFix
Writing int g[][]Compile error: incomplete element typeGive the column count, int g[][5]
Using int **g as the parameterIncompatible pointer type; crash if forcedA 2-D array is not an array of pointers; use int (*g)[5]
Wrong column count in the calleeReads the wrong elements silentlyDefine the width once with a macro or constant
Forgetting the row countLoops overrun the matrixPass rows as an argument

Best Practices, Performance and Security

  • Define the column count with #define or an enum constant so the caller and callee always agree.
  • Pass the row count separately; it is not carried in the parameter type.
  • Use const on the matrix when the function only reads it.
  • For performance, iterate row by row with the column index innermost; that follows memory order and is friendly to caches on larger targets.
  • Bounds-check any index that originates from user or external input before using it in g[r][c].

Key Takeaways

  • A 2-D array parameter becomes a pointer to a row.
  • Only the leftmost dimension may be omitted; the others define the row size.
  • Pointer steps on that parameter jump a whole row at a time.
  • int ** is a different type and is not compatible with a true 2-D array.

Conclusion

To pass a 2D array to a function in C, give the function the column count in its type and the row count as a separate argument. Remember that the function holds a pointer to a row, which is why the column count is non-negotiable and why int ** does not fit. With those two facts, matrix code in firmware becomes predictable. Next we look at a different data structure that is easy to confuse with a 2-D array: the array of pointers.

Suggested Images for This Lecture

  • Diagram of a pointer stepping across three rows. Alt text: “pass 2D array to function in C pointer to row stepping 20 bytes”.
  • Terminal screenshot of the thermal grid output. Alt text: “free C tutorial passing a 2D array to a function in C program output”.
  • Comparison chart of row pointer, C99 parameter and flat array. Alt text: “embedded C programming ways to pass a matrix to a function”.

Frequently Asked Questions

Why can I omit only the first dimension?

The first dimension is never needed to locate an element. All later dimensions determine how far apart rows are, so the compiler must know them.

What type does a 2-D array parameter really have?

A pointer to an array of the column size, for example int (*g)[5] for a matrix with five columns.

Can I pass a 2-D array to a function expecting int **?

No. An int ** expects a pointer to pointers, but a true 2-D array is one contiguous block. The types are not compatible.

Does the function copy the matrix?

No. It receives a pointer to the first row, and modifications affect the caller’s original matrix.

How do I support different column counts in one function?

Use a C99 parameter with the sizes declared first, or use a flat array with explicit row and column arguments.

Is a 2-D array stored row by row?

Yes. C uses row-major order, so the last index changes fastest in memory.

Master Pointers With the Free Embedded C Course

Next we study the array of pointers, a structure used for string tables, register maps and command lists in real firmware.

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 *