21

If I allocate a 2D array like this int a[N][N]; it will allocate a contiguous block of memory.

But if I try to do it dynamically like this :

int **a = malloc(rows * sizeof(int*));
for(int i = 0; i < rows; i++) 
   a[i] = malloc(cols * sizeof(int));

This maintains a unit stride between the elements in the rows, but this may not be the case between rows.

One solution is to convert from 2D to 1D, besides that, is there another way to do it?

dreamcrash
  • 47,137
  • 25
  • 94
  • 117
  • 1
    That's not a C multidimensional array, it's an array of pointers to separate rows. Use pointer to a C99 VLA. [Freaky way of allocating two-dimensional array?](https://stackoverflow.com/q/36794202) – Peter Cordes Mar 31 '21 at 14:19

7 Answers7

23

If your array dimensions are known at compile time:

#define ROWS ...
#define COLS ...

int (*arr)[COLS] = malloc(sizeof *arr * ROWS);
if (arr) 
{
  // do stuff with arr[i][j]
  free(arr);
}

If your array dimensions are not known at compile time, and you are using a C99 compiler or a C2011 compiler that supports variable length arrays:

size_t rows, cols;
// assign rows and cols
int (*arr)[cols] = malloc(sizeof *arr * rows);
if (arr)
{
  // do stuff with arr[i][j]
  free(arr);
}

If your array dimensions are not known at compile time, and you are not using a C99 compiler or a C2011 compiler that supports variable-length arrays:

size_t rows, cols;
// assign rows and cols
int *arr = malloc(sizeof *arr * rows * cols);
{
  // do stuff with arr[i * rows + j]
  free(arr);
}
John Bode
  • 119,563
  • 19
  • 122
  • 198
9

In fact, n-dimensional arrays (allocated on the stack) are really just 1-dimension vectors. The multiple indexing is just syntactic sugar. But you can write an accessor function to emulate something like what you want:

int index_array(int *arr, size_t width, int x, int y)
{
    return arr[x * width + y];
}

const size_t width = 3;
const size_t height = 2;
int *arr = malloc(width * height * sizeof(*arr));

// ... fill it with values, then access it:

int arr_1_1 = index_array(arr, width, 1, 1);

However, if you have C99 support, then declaring a pointer to an array is possible, and you can even use the syntactic sugar:

int (*arr)[width] = malloc(sizeof((*arr) * height);
arr[x][y] = 42;
7

Say you want to dynamically allocate a 2-dimensional integer array of ROWS rows and COLS columns. Then you can first allocate a continuous chunk of ROWS * COLS integers and then manually split it into ROWS rows. Without syntactic sugar, this reads

int *mem = malloc(ROWS * COLS * sizeof(int));
int **A = malloc(ROWS * sizeof(int*));
for(int i = 0; i < ROWS; i++) 
   A[i] = mem + COLS*i;
// use A[i][j]

and can be done more efficiently by avoiding the multiplication,

int *mem = malloc(ROWS * COLS * sizeof(int));
int **A = malloc(ROWS * sizeof(int*));
A[0] = mem;
for(int i = 1; i < ROWS; i++) 
   A[i] = A[i-1] + COLS;
// use A[i][j]

Finally, one could give up the extra pointer altogether,

int **A = malloc(ROWS * sizeof(int*));
A[0] = malloc(ROWS * COLS * sizeof(int));
for(int i = 1; i < ROWS; i++) 
   A[i] = A[i-1] + COLS;
// use A[i][j]

but there's an important GOTCHA! You would have to be careful to first deallocate A[0] and then A,

free(A[0]);
free(A);              // if this were done first, then A[0] would be invalidated

The same idea can be extended to 3- or higher-dimensional arrays, although the code will get messy.

Matei Tene
  • 91
  • 1
  • 4
  • Code that uses this still has to go through the extra indirection of the array of row-pointers. I wouldn't recommend this unless you want to be able to swap rows by rearranging pointer elements. – Peter Cordes Mar 31 '21 at 14:43
  • And BTW, compilers will normally do the "avoiding multiplication" optimization for you, doing `tmp += COLS` instead of `i*COLS`. https://en.wikipedia.org/wiki/Strength_reduction. Your way of writing it as `A[i-1]+COLS` should also optimize ok, but I wouldn't bother. – Peter Cordes Mar 31 '21 at 14:44
3

You can treat dynamically allocated memory as an array of a any dimension by accessing it in strides:

int * a = malloc(sizeof(int) * N1 * N2 * N3);  // think "int[N1][N2][N3]"

a[i * N2 * N3 + j * N3 + k] = 10;              // like "a[i, j, k]"
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
2

The best way is to allocate a pointer to an array,

int (*a)[cols] = malloc(rows * sizeof *a);
if (a == NULL) {
    // alloc failure, handle or exit
}

for(int i = 0; i < rows; ++i) {
    for(int j = 0; j < cols; ++j) {
        a[i][j] = i+j;
    }
}

If the compiler doesn't support variable length arrays, that only works if cols is a constant expression (but then you should upgrade your compiler anyway).

Daniel Fischer
  • 181,706
  • 17
  • 308
  • 431
  • Phrasing: this is allocating *space* for a 2D array, or more simply "allocating a true 2D array". The *pointer* to it (`a`) is a local variable in automatic storage, so doesn't have to be manually allocated. – Peter Cordes Mar 31 '21 at 14:38
2

Excuse my lack of formatting or any mistakes, but this is from a cellphone.

I also encountered strides where I tried to use fwrite() to output using the int** variable as the src address.

One solution was to make use of two malloc() invocations:

#define HEIGHT 16
#define WIDTH 16

.
.
.
//allocate
int **data = malloc(HEIGHT * sizeof(int **));
int *realdata = malloc(HEIGHT * WIDTH * sizeof(int));

//manually index
for (int i = 0; i < HEIGHT; i++)
    data[i] = &realdata[i * WIDTH];

//populate
int idx = 0;
for (int i = 0; i < HEIGHT; i++)
    for (int j = 0; j < WIDTH; j++)
        data[i][j] = idx++;

//select
int idx = 0;
for (int i = 0; i < HEIGHT; i++)
{
    for (int j = 0; j < WIDTH; j++)
        printf("%i, ", data[i][j]);
    printf("/n");
}

//deallocate
.
.
.
136
  • 1,083
  • 1
  • 7
  • 14
Skullquake
  • 41
  • 2
1

You can typedef your array (for less headake) and then do something like that:

#include <stdlib.h>
#define N 10
typedef int A[N][N];
int main () {
  A a; // on the stack
  a[0][0]=1;
  A *b=(A*)malloc (sizeof(A)); // on the heap
  (*b)[0][0]=1;
}
pbhd
  • 4,384
  • 1
  • 20
  • 26