Arrays in C
Learn 1D and 2D arrays, array decay to pointers, variable-length arrays, and string arrays in C.
Declaring and Initializing Arrays
An array is a contiguous block of memory holding multiple values of the same type. Arrays are the simplest and most cache-friendly data structure in C — all elements sit adjacent in memory, so traversing them is very fast. The trade-off is that the size must be known at compile time (for stack arrays) and cannot change once allocated.
#include <stdio.h>
int main(void) {
/* Declaration — elements are uninitialized (contain garbage) */
int scores[5];
/* Declaration with initialization */
int primes[6] = {2, 3, 5, 7, 11, 13};
/* Partial initialization — remaining elements are set to zero */
int data[10] = {1, 2, 3}; /* data[3..9] == 0 */
/* Zero-initialize the entire array */
int zeroes[100] = {0};
/* Let the compiler count the elements from the initializer */
double temps[] = {36.5, 37.1, 38.2, 36.9};
int n = sizeof(temps) / sizeof(temps[0]); /* 4 */
/* Designated initializers (C99) — initialize specific indices by name */
int days_in_month[13] = {
[1] = 31, [2] = 28, [3] = 31,
[4] = 30, [5] = 31, [6] = 30,
[7] = 31, [8] = 31, [9] = 30,
[10] = 31, [11] = 30, [12] = 31
};
for (int i = 0; i < n; i++) {
printf("temps[%d] = %.1f\n", i, temps[i]);
}
return 0;
}
Iterating Over Arrays
The most common pattern is a for loop with an index from 0 to n-1. Always pass the length separately when passing arrays to functions — the array itself carries no size information.
#include <stdio.h>
void print_array(const int *arr, int n) {
for (int i = 0; i < n; i++) {
printf("%d", arr[i]);
if (i < n - 1) printf(", ");
}
printf("\n");
}
int find_max(const int *arr, int n) {
int max = arr[0];
for (int i = 1; i < n; i++) {
if (arr[i] > max) max = arr[i];
}
return max;
}
int main(void) {
int nums[] = {42, 17, 93, 5, 68, 31};
int len = sizeof(nums) / sizeof(nums[0]);
print_array(nums, len);
printf("Max: %d\n", find_max(nums, len));
return 0;
}
Array Decay to Pointer
When an array is used in most expressions — especially when passed to a function — it automatically converts to a pointer to its first element. This is called “decay.” The consequence is that the function loses the array’s size information, which is why you must always pass the length as a separate parameter.
#include <stdio.h>
void size_demo(int arr[], int n) {
/* sizeof(arr) is sizeof(int*) here — the array has decayed to a pointer */
printf("Inside function: sizeof(arr) = %zu\n", sizeof(arr));
printf("n = %d\n", n);
}
int main(void) {
int data[10] = {0};
printf("In main: sizeof(data) = %zu\n", sizeof(data)); /* 40 bytes */
size_demo(data, 10); /* sizeof(arr) = 8 (pointer size) */
return 0;
}
All three of these function signatures are identical — the array syntax is just syntactic sugar for a pointer:
void f(int arr[], int n); /* array syntax — decays to pointer */
void f(int *arr, int n); /* explicit pointer — identical */
void f(int arr[10], int n); /* the 10 is ignored by the compiler */
2D Arrays
A 2D array in C is stored in row-major order — all elements of row 0, then all of row 1, and so on. This means iterating row by row (outer loop over rows, inner loop over columns) is cache-friendly, while column-major access causes cache misses.
#include <stdio.h>
#define ROWS 3
#define COLS 4
void print_matrix(int mat[ROWS][COLS]) {
for (int r = 0; r < ROWS; r++) {
for (int c = 0; c < COLS; c++) {
printf("%4d", mat[r][c]);
}
printf("\n");
}
}
void multiply(int a[2][3], int b[3][2], int result[2][2]) {
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
result[i][j] = 0;
for (int k = 0; k < 3; k++) {
result[i][j] += a[i][k] * b[k][j];
}
}
}
}
int main(void) {
int matrix[ROWS][COLS] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
print_matrix(matrix);
/* 2D arrays are stored contiguously in row-major order */
/* matrix[r][c] == *(&matrix[0][0] + r * COLS + c) */
return 0;
}
For 2D arrays passed to functions, all dimensions except the first must be specified so the compiler can compute the row stride:
/* The number of columns must be a compile-time constant */
void process(int mat[][4], int rows) { /* ... */ }
For dynamic 2D arrays where dimensions are not known at compile time, use a flat 1D array with manual index calculation:
#include <stdlib.h>
/* Allocate a rows×cols matrix as a single flat array */
int *make_matrix(int rows, int cols) {
return malloc((size_t)rows * cols * sizeof(int));
}
/* Access element at (r, c) with the flat-index formula */
#define MAT(m, cols, r, c) m[(r)*(cols) + (c)]
int main(void) {
int rows = 4, cols = 5;
int *m = make_matrix(rows, cols);
MAT(m, cols, 2, 3) = 42; /* equivalent to m[2][3] = 42 */
free(m);
return 0;
}
Variable-Length Arrays (VLAs)
C99 introduced variable-length arrays, whose size is determined at runtime rather than compile time. They are convenient for small, temporary arrays whose size depends on user input or function parameters — but they allocate on the stack, so they are unsafe for large sizes.
#include <stdio.h>
void print_vla(int n) {
int arr[n]; /* size known only at runtime — allocated on the stack */
for (int i = 0; i < n; i++) {
arr[i] = i * i;
}
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main(void) {
print_vla(5); /* 0 1 4 9 16 */
print_vla(8); /* 0 1 4 9 16 25 36 49 */
return 0;
}
Caution: VLAs are stack-allocated. Never use them for large or unbounded sizes — a stack overflow has no safe recovery. VLAs were made optional in C11 (__STDC_NO_VLA__ is defined if not supported). For large dynamic arrays, always use malloc.
Arrays of Strings
An array of strings in C is typically an array of const char * pointers, each pointing to a string literal. This is memory-efficient (no copying) but the strings are read-only.
#include <stdio.h>
int main(void) {
/* Array of pointers to string literals — read-only, variable-length strings */
const char *days[] = {
"Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday", "Sunday"
};
int ndays = sizeof(days) / sizeof(days[0]);
for (int i = 0; i < ndays; i++) {
printf("Day %d: %s\n", i + 1, days[i]);
}
/* 2D char array — fixed-size buffers that you can modify */
char names[3][20] = {"Alice", "Bob", "Charlie"};
for (int i = 0; i < 3; i++) {
printf("%s\n", names[i]);
}
return 0;
}
The difference matters: const char *days[] is an array of pointers to string literals (read-only, variable-length strings). char names[3][20] is a true 2D array of characters (writable, fixed-size buffers).
Sorting Arrays
The standard library provides qsort for sorting any array. It uses a comparison function pointer, making it generic — you supply the logic for comparing two elements.
#include <stdio.h>
#include <stdlib.h>
/* Comparison function: returns negative if a < b, 0 if equal, positive if a > b */
int compare_int(const void *a, const void *b) {
int ia = *(const int *)a;
int ib = *(const int *)b;
return (ia > ib) - (ia < ib); /* safe: returns -1, 0, or 1 without overflow */
}
int compare_desc(const void *a, const void *b) {
return compare_int(b, a); /* reverse the arguments for descending order */
}
int main(void) {
int arr[] = {64, 25, 12, 22, 11};
int n = sizeof(arr) / sizeof(arr[0]);
qsort(arr, n, sizeof(int), compare_int);
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]); /* 11 12 22 25 64 */
}
printf("\n");
return 0;
}