Passing arrays to functions is one of the most fundamental yet misunderstood topics in C programming. Unlike primitive scalar types (like int or float) which are passed by value, arrays in C cannot be passed directly by value as a complete block of memory. Instead, an array name automatically decays into a pointer to its first element when passed as a function parameter.
Array Parameter Syntax Quick Reference
In function declarations, the three array parameter syntaxes below are identical to the C compiler—all three receive a memory address pointer:
/* All 3 function prototypes receive a pointer (int *arr) and behave identically */
void process_1(int arr[], size_t size); /* Unbounded array bracket syntax */
void process_2(int arr[10], size_t size); /* Size inside brackets is ignored by compiler */
void process_3(int *arr, size_t size); /* Explicit pointer syntax (preferred in production) */Understanding Pointer Decay: Memory Layout Mechanics
When an array is declared on the stack, it occupies contiguous memory. Passing it to a function copies only the 8-byte memory address of the first element (&arr[0]), leaving the original data in place:
+---------------------------------------------------------------------------------+
| CALLER STACK FRAME (main function) |
| int numbers[4] = { 10, 20, 30, 40 }; |
| Memory Addr: 0x7fff1000 [ 10 | 20 | 30 | 40 ] (Occupies 16 bytes) |
+---------------------------------------------------------------------------------+
|
| Calling modify_array(numbers, 4)
v
+---------------------------------------------------------------------------------+
| CALLEE STACK FRAME (modify_array function) |
| int *arr = 0x7fff1000; (Receives 8-byte pointer addressing 0x7fff1000) |
| Dereferencing arr[0] directly mutates caller's memory at 0x7fff1000! |
+---------------------------------------------------------------------------------+1. Passing Entire Arrays vs Individual Elements
You can pass an entire array by passing its base pointer, or pass individual elements by value or by reference:
#include <stdio.h>
// 1. Pass by Value: Receives a copy of a single integer element
void print_single_val(int val) {
printf("Single value copy: %d\n", val);
}
// 2. Pass by Reference: Receives a pointer to modify a single element
void update_single_val(int *val_ptr) {
*val_ptr += 100;
}
// 3. Pass Entire Array: Receives pointer to first element and mutates array
void scale_array(int *arr, size_t size, int factor) {
for (size_t i = 0; i < size; i++) {
arr[i] *= factor;
}
}
int main(void) {
int data[] = { 5, 10, 15 };
size_t len = sizeof(data) / sizeof(data[0]);
print_single_val(data[1]); // Pass element by value (10)
update_single_val(&data[0]); // Pass element by reference (modifies data[0] to 105)
scale_array(data, len, 2); // Scale entire array
printf("Updated array: %d, %d, %d\n", data[0], data[1], data[2]); // Output: 210, 20, 30
return 0;
}Key Insights from This Implementation:
Element Copying: Passing
data[1]passes a copy of the integer value10. Modifyingvalinsideprint_single_val()has zero effect on the originaldataarray.Address-Of Operator `&`: Passing
&data[0]explicitly passes the memory address of the first slot, allowingupdate_single_val()to mutate the original caller value.Array Length Parameter: Always pass
size_t sizeas a separate argument alongside the array pointer becausesizeof(arr)inside a receiving function returns the pointer size (8 bytes), not the full array byte length.
2. Protecting Array Read-Only Access with const Pointers
When a function needs to inspect or search an array without modifying its contents, qualify the pointer parameter with const int *arr. This instructs the C compiler to reject any accidental assignment or mutation:
#include <stdio.h>
// Function reads array elements safely without side effects
int sum_elements(const int *arr, size_t size) {
int total = 0;
for (size_t i = 0; i < size; i++) {
// arr[i] = 0; // Compiler Error: assignment of read-only location
total += arr[i];
}
return total;
}
int main(void) {
int scores[] = { 88, 92, 79, 95 };
size_t count = sizeof(scores) / sizeof(scores[0]);
int result = sum_elements(scores, count);
printf("Total Sum: %d\n", result); // Output: 354
return 0;
}Why Const Matters in Production C:
API Contract Security: Marking array parameters
constguarantees callers that their data structure will remain untouched.Compiler Verification: Attempting to write to
arr[i]triggers an immediate compile-time error (read-only location assignment), preventing subtle runtime bugs.
3. Passing 2D Multidimensional Arrays (Row Stride Math)
When passing 2D arrays (int matrix[3][4]), the compiler requires the column size N in the parameter declaration (int arr[][4]) to perform row stride address arithmetic:
#include <stdio.h>
#define COLS 3
// Fixed-column 2D array parameter
void print_matrix_2d(int arr[][COLS], size_t rows) {
for (size_t i = 0; i < rows; i++) {
for (size_t j = 0; j < COLS; j++) {
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
// C99 Variable Length Array (VLA) parameter syntax
void print_dynamic_matrix(size_t rows, size_t cols, int matrix[rows][cols]) {
for (size_t i = 0; i < rows; i++) {
for (size_t j = 0; j < cols; j++) {
printf("%02d ", matrix[i][j]);
}
printf("\n");
}
}
int main(void) {
int grid[2][3] = {
{ 1, 2, 3 },
{ 4, 5, 6 }
};
printf("Fixed 2D Matrix:\n");
print_matrix_2d(grid, 2);
printf("C99 VLA Dynamic Matrix:\n");
print_dynamic_matrix(2, 3, grid);
return 0;
}Under the Hood: Row Stride Address Arithmetic:
Row Stride Formula: To locate
arr[i][j]in memory, the compiler calculates:element_addr = base_addr + (i * COLS + j) * sizeof(element). OmittingCOLSbreaks this calculation.C99 VLA Flexibility: In C99, placing
size_t rows, size_t cols*before*int matrix[rows][cols]allows functions to process 2D matrices of arbitrary runtime dimensions.
Troubleshooting & Common Pitfalls Checklist
Out-of-Bounds Memory Corruption - C does not perform array boundary checking. Passing an incorrect
sizeargument allows function loops to read/write past the allocated stack buffer into adjacent memory.Returning Local Stack Arrays - Never return a pointer to a local automatic array (
int local[10]) from a function. Local stack memory is destroyed upon function return, resulting in a dangling pointer.Incompatible Pointer Types in 2D Arrays - Passing
int **matrixto a function expectingint matrix[3][4]is invalid because a pointer-to-pointer (int**) does not match contiguous 2D memory stride.
Mastering array decay, pointer arithmetic, and const qualification empowers C developers to build safe, high-performance systems and embedded applications.
Comments and corrections