Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
Passing 2D Arrays to Functions
Why the compiler insists on every size except the first, and how to write safe matrix helpers.
In this free C tutorial you will learn how to pass 2D array to function in C without fighting the compiler. Almost every embedded C programming project has a helper that works on a table: a calibration grid, a key matrix, a frame buffer. Once you understand which sizes the parameter list must contain, matrix helpers become routine. This lecture belongs to our free embedded C course and builds directly on the multidimensional arrays you just studied.
What You Will Learn
- The rule: only the leftmost dimension may be empty in a parameter
- Why the compiler needs the other sizes, explained with address arithmetic
- The exact error message you get when you break the rule
- Writing helpers that read and modify a 2D table
- The sizeof trap inside functions
- C99 parameter sizes for matrices of any shape
Prerequisites
Lectures on 1D arrays, 2D arrays and 3D arrays, plus functions with parameters and return values from the Functions chapter.
How to Pass 2D Array to Function in C: The Rule
When you pass a one-dimensional array you write int a[] and everything works. For higher dimensions the rule is: the leftmost size may be omitted, every other size must be written. So a two-dimensional parameter looks like int m[][4] and a three-dimensional one looks like int c[][3][4].
| Parameter declaration | Valid? | Reason |
|---|---|---|
int a[] | Yes | 1D array, nothing else to know |
int m[][4] | Yes | Row length 4 is known |
int m[3][4] | Yes | Allowed, but the 3 is ignored by the compiler |
int m[][] | No | Row length unknown |
int m[3][] | No | Row length still unknown |
int c[][3][4] | Yes | Only the leftmost size omitted |
int c[][][4] | No | Middle size missing |
Pass 2D Array to Function in C: Why the First Size Is Optional
To read m[i][j] the compiler has to compute an address. It starts at the first element, skips i complete rows and then j more elements. Skipping a complete row means adding the length of a row in bytes. That length is columns times sizeof(int), so the column count must be known. The number of rows is never needed for the address calculation. It only matters for deciding when to stop, and that is your loop’s job.
The highlighted chip is m[2][1]. It lies two full rows (2 x 4 elements) plus one element after the start. Remove the 4 from the formula and nothing can be computed, which is exactly what the compiler tells you.
What the compiler says when you break the rule
#include <stdio.h>
/* Wrong: the compiler cannot compute where row 1 starts without the column count. */
static int first_of_row1(int m[][])
{
return m[1][0];
}
int main(void)
{
int t[2][3] = { {1, 2, 3}, {4, 5, 6} };
printf("%d\n", first_of_row1(t));
return 0;
}
gcc -std=c11 -Wall -Wextra -o ep_bad_param ep_bad_param.c
ep_bad_param.c:4:30: error: array type has incomplete element type 'int[]'
4 | static int first_of_row1(int m[][])
| ^
ep_bad_param.c:4:30: note: declaration of 'm' as multidimensional array must have bounds for all dimensions except the first
Read the note: must have bounds for all dimensions except the first. The rule you learned is printed right in the error.
Pass 2D Array to Function in C: Writing Helpers
The program below keeps a 3 x 4 table of readings and offers four helpers. Notice that every helper repeats the column count through the macro COLS, so a change to the table width is made in one place.
#include <stdio.h>
#define COLS 4
/* Leftmost size may be left empty; every other size must be given. */
static int row_total(int m[][COLS], int row)
{
int total = 0;
for (int c = 0; c < COLS; c++)
total += m[row][c];
return total;
}
static int grid_max(int m[][COLS], int rows)
{
int best = m[0][0];
for (int r = 0; r < rows; r++)
for (int c = 0; c < COLS; c++)
if (m[r][c] > best)
best = m[r][c];
return best;
}
/* The callee works on the caller's memory, so this really changes it. */
static void scale(int m[][COLS], int rows, int factor)
{
for (int r = 0; r < rows; r++)
for (int c = 0; c < COLS; c++)
m[r][c] *= factor;
}
/* A 3-D parameter: only the first size is optional. */
static int cube_sum(int cube[][3][COLS], int planes)
{
int sum = 0;
for (int p = 0; p < planes; p++)
for (int r = 0; r < 3; r++)
for (int c = 0; c < COLS; c++)
sum += cube[p][r][c];
return sum;
}
/* Size seen inside a function is the size of a pointer, not of the array. */
static void show_size(int m[][COLS])
{
printf("inside function : sizeof(m) = %zu, sizeof(m[0]) = %zu\n",
sizeof m, sizeof m[0]);
}
int main(void)
{
int table[3][COLS] = {
{ 4, 9, 2, 7 },
{ 1, 8, 6, 3 },
{ 5, 12, 10, 11 }
};
int cube[2][3][COLS] = {
{ {1, 1, 1, 1}, {2, 2, 2, 2}, {3, 3, 3, 3} },
{ {4, 4, 4, 4}, {5, 5, 5, 5}, {6, 6, 6, 6} }
};
printf("in main : sizeof(table) = %zu\n", sizeof table);
show_size(table);
printf("row 2 total : %d\n", row_total(table, 2));
printf("largest value : %d\n", grid_max(table, 3));
scale(table, 3, 10);
printf("after scale x10 : table[0][0] = %d, table[2][3] = %d\n",
table[0][0], table[2][3]);
printf("cube sum : %d\n", cube_sum(cube, 2));
return 0;
}
gcc -std=c11 -Wall -Wextra -o ep_matrix_args ep_matrix_args.c
$ ./ep_matrix_args
in main : sizeof(table) = 48
inside function : sizeof(m) = 8, sizeof(m[0]) = 16
row 2 total : 38
largest value : 12
after scale x10 : table[0][0] = 40, table[2][3] = 110
cube sum : 84
Row 2 holds 5 + 12 + 10 + 11, which is 38. The largest value in the whole table is 12. After scale(table, 3, 10) the first element becomes 40 and the last becomes 110, and these changes are visible in main because the helper worked on the original memory. Each row of the cube repeats one value (1, 2, 3 in the first plane and 4, 5, 6 in the second), so the sum is 4 x (1+2+3+4+5+6) = 84.
Arrays are not copied
When you write row_total(table, 2) the name table turns into a pointer to its first row, of type int (*)[4]. The function gets that pointer and nothing else, so no element is copied and the call costs the same for a 3-row table or a 3000-row table. The flip side is that a function can silently change your data. If a helper should only read, say so with const.
const int m[][4] and passing a plain int t[3][4] triggers a pedantic warning in C before C23 (“invalid use of pointers to arrays with different qualifiers”). It is harmless in practice, but if you build with -pedantic you will see it. This lecture leaves const off 2D parameters to keep the build clean.The sizeof Trap Inside a Function
In main the expression sizeof table is 48, which is 3 rows x 4 columns x 4 bytes. Inside show_size the same expression gives 8. The output of our program shows both numbers side by side, and gcc even warns you at compile time:
warning: 'sizeof' on array function parameter 'm' will return size of 'int (*)[4]' [-Wsizeof-array-argument]
The parameter was never an array. It is a pointer to a row of four ints, 8 bytes on a 64-bit PC and 4 bytes on a 32-bit microcontroller. Notice that sizeof m[0] still gives 16, because one row is a real array of four ints. The lesson is simple: always pass the row count as a separate argument.
Arrays of Any Shape with C99 Parameters
A fixed COLS means the helper only works for tables four columns wide. C99 lets you pass the sizes first and use them in the next parameter:
#include <stdio.h>
/* C99 style: sizes travel as earlier parameters, so any shape works. */
static void print_grid(int rows, int cols, int g[rows][cols])
{
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++)
printf("%3d", g[r][c]);
printf("\n");
}
}
int main(void)
{
int a[2][3] = { {1, 2, 3}, {4, 5, 6} };
int b[3][2] = { {7, 8}, {9, 10}, {11, 12} };
print_grid(2, 3, a);
printf("--\n");
print_grid(3, 2, b);
return 0;
}
$ ./ep_vla_param
1 2 3
4 5 6
--
7 8
9 10
11 12
One function now prints a 2 x 3 table and a 3 x 2 table. The order matters: rows and cols must appear before the array parameter that uses them. Note that this is only a parameter type, so no extra memory is created. Variable length arrays as local variables are a different story and are best avoided on small stacks. Also remember that VLA support became optional in C11, so check your embedded toolchain.
Use Cases in Embedded Firmware
- Keypad scanning: a helper that receives a
char keymap[][4]and a row and column from the scan loop. - Calibration grids: a lookup function taking
conststyle tables stored in flash. - Display buffers: clear, fill and copy helpers for pixel matrices.
- Filter coefficients: one function that applies any row of a coefficient table.
Common Mistakes When You Pass 2D Array to Function in C
| Mistake | Symptom | Fix |
|---|---|---|
| Writing int m[][] | Compile error: incomplete element type | Give the column count |
| Passing int **p for a real 2D array | Garbage values or crash | A 2D array is not an array of pointers; use int m[][N] or int (*m)[N] |
| Using sizeof on the parameter | Gets pointer size, loops stop early | Pass the row count |
| Mismatched column count between caller and callee | Reads from shifted positions | Share one macro or constant |
| Modifying data in a helper that should only read | Hard to find data changes | Document it, or use const where the build allows |
Best Practices, Performance and Security
- Define the column count once with
#defineorenumand reuse it in every prototype. - Pass the row count explicitly, and pass column count too when you use C99 style parameters.
- Pass a pointer, never copy: large tables cost the same to pass as small ones, so there is no performance reason to flatten them.
- Use unsigned or
size_tfor sizes so negative counts cannot sneak in. - Check every row or column index that comes from outside the program before the call; the function itself cannot check bounds.
From a security angle, a helper that receives a row count from a communication packet must verify it against the real table size. The compiler trusts the number you pass, and a wrong count turns a harmless loop into an out-of-bounds read or write.
Summary and Key Takeaways
- To pass 2D array to function in C, only the leftmost size of the parameter may be omitted.
- The remaining sizes are needed to compute where each row starts.
- The function gets a pointer to a row, so the array is shared, not copied.
- sizeof of a parameter gives the pointer size; pass the row count yourself.
- C99 lets you pass sizes first and use them to describe the array parameter.
Conclusion
You now know how to pass 2D array to function in C and how to explain, with address arithmetic, why the rule exists. That understanding carries straight into the next topic: strings. A string is a char array, and a function that receives a string receives it in exactly the way you saw here, as a pointer to the first element.
Frequently Asked Questions
Which sizes can I leave empty when I pass 2D array to function in C?
Only the leftmost one. For a 2D parameter write int m[][4]; for a 3D parameter write int c[][3][4]. Every other size must be a constant the compiler can see.
Why does int m[][] give an error?
To find m[i][j] the compiler multiplies i by the length of a row, so it must know the number of columns. Without it the element type is incomplete, and gcc reports ‘array type has incomplete element type’.
Does the function receive a copy of the array?
No. The array name decays to a pointer to its first row, so the function works on the caller’s memory. Changes made inside the function are visible in the caller.
Why is sizeof of a 2D parameter only 8?
Inside the function the parameter is really a pointer to a row, int (*)[4]. On a 64-bit system a pointer is 8 bytes. Pass the row count as a separate argument.
Can I pass arrays of different widths to the same function?
Not with a fixed column count. Use C99 style parameters such as int rows, int cols, int g[rows][cols], or flatten the data into a one-dimensional array with explicit dimensions.
Should I use C99 variable length array parameters on a microcontroller?
The parameter form is fine because it only describes a pointer type. The danger is declaring variable length arrays as local variables, which can overflow a small stack.
Where does this fit in the free embedded C course?
It follows the lecture on 3D arrays and prepares you for strings, because a string function receives a char array the same way.
Continue the Free Embedded C Course
Strings are next. Keep going through the free C programming course and build the embedded C programming foundation that interviews ask about.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments