Dynamic 2D Arrays in C

In C, when the size of a 2D array is not known at compile time (rows or columns decided at runtime), we must use **dynamic memory allocation** (malloc/calloc).

There are **several popular approaches**, each with different memory layout, performance, and ease of use.

Comparison of Different Methods

MethodContiguous Memory?Access SyntaxEase of UseCache FriendlyFree Complexity
Double Pointer (Array of Pointers)No (jagged possible)arr[i][j]Very EasyPoorNeed loop
Single Block + Pointer ArithmeticYesarr[i*n + j]ModerateExcellentSingle free
Single Block + Array of PointersYesarr[i][j]EasyGoodSingle free
1D Array + Manual MappingYesarr[i*n + j]FastestExcellentSingle free

Method 1: Double Pointer (Most Common & Easiest)

C
Double pointer (array of row pointers)
int rows = 4, cols = 5;

int **arr = (int**)malloc(rows * sizeof(int*));
if(!arr) { /* error */ }

for(int i = 0; i < rows; i++) {
    arr[i] = (int*)malloc(cols * sizeof(int));
    if(!arr[i]) { /* error handling */ }
}

// Use like normal 2D array
arr[2][3] = 42;

// Free memory (important!)
for(int i = 0; i < rows; i++) {
    free(arr[i]);
}
free(arr);
Note: Memory is **not contiguous** → poor cache performance in large matrices

Method 2: Single Contiguous Block + Pointer Arithmetic (Best Performance)

C
int rows = 4, cols = 5;

int *arr = (int*)malloc(rows * cols * sizeof(int));
if(!arr) { /* error */ }

// Access using pointer arithmetic
*(arr + i*cols + j) = 100;

// Or with macro for readability
#define ARR(i,j) arr[(i)*cols + (j)]
ARR(2,3) = 42;

// Free - only one call!
free(arr);
Note: Best cache performance, single allocation, single free → very efficient

Method 3: Single Block + Array of Row Pointers (Best of Both Worlds)

C
Contiguous memory + normal arr[i][j] syntax
int rows = 4, cols = 5;

int **arr = (int**)malloc(rows * sizeof(int*));
int *data = (int*)malloc(rows * cols * sizeof(int));

for(int i = 0; i < rows; i++) {
    arr[i] = data + i * cols;
}

// Use normally
arr[1][2] = 999;

// Free only two pointers
free(data);
free(arr);
Note: Most recommended in modern code – combines clean syntax with contiguous memory

Best Practices & Recommendations (2025+)

  1. Always check malloc/calloc return value
  2. Use **Method 3** (single block + row pointers) for most cases
  3. Use **Method 2** when performance is critical (matrix operations, ML, graphics)
  4. Never forget to free all allocated memory
  5. Consider using a struct to encapsulate rows, cols, and data pointer
  6. For very large matrices → consider libraries (like OpenBLAS, custom allocators)