How do I work with dynamic multi-dimensional arrays in C?
Asked Answered
N

9

66

Does someone know how I can use dynamically allocated multi-dimensional arrays using C? Is that possible?

Netti answered 27/5, 2009 at 20:10 Comment(0)
S
82

With dynamic allocation, using malloc:

int** x;

x = malloc(dimension1_max * sizeof(*x));
for (int i = 0; i < dimension1_max; i++) {
  x[i] = malloc(dimension2_max * sizeof(x[0]));
}

//Writing values
x[0..(dimension1_max-1)][0..(dimension2_max-1)] = Value; 
[...]

for (int i = 0; i < dimension1_max; i++) {
  free(x[i]);
}
free(x);

This allocates an 2D array of size dimension1_max * dimension2_max. So, for example, if you want a 640*480 array (f.e. pixels of an image), use dimension1_max = 640, dimension2_max = 480. You can then access the array using x[d1][d2] where d1 = 0..639, d2 = 0..479.

But a search on SO or Google also reveals other possibilities, for example in this SO question

Note that your array won't allocate a contiguous region of memory (640*480 bytes) in that case which could give problems with functions that assume this. So to get the array satisfy the condition, replace the malloc block above with this:

int** x;
int* temp;

x = malloc(dimension1_max * sizeof(*x));
temp = malloc(dimension1_max * dimension2_max * sizeof(x[0]));
for (int i = 0; i < dimension1_max; i++) {
  x[i] = temp + (i * dimension2_max);
}

[...]

free(temp);
free(x);
Sorbose answered 27/5, 2009 at 20:19 Comment(15)
This won't compile, you need to declare x as "int **", not "int[][]".Yarn
What does this exactly do? What is dimension1_max and dimension2_max? What does the first "for"?Netti
Thanks, Adam, I knew something was wrong although I already corrected the first sizeof to int* instead of int.Sorbose
It is not a multidimensional array - it is array of pointers to int, or array of arrays. To allocate memory for real 2D array you need to use malloc(dim1 * dim2 * sizeof(int)). If some function expects pointer to 2D array, like foo(int * bar[5][6]) and you pass your x, weird things will happen. See en.wikipedia.org/wiki/C_syntax#Multidimensional_arraysDrennan
Ah, I get what you mean. I've added another example where a contiguous region of memory is provided and also corrected the malloc calls that weren't casted to int*/int**.Sorbose
Why are you casting mallocAnya
@JayB: In pure C, you don't cast malloc, but in C++ it is required, see #606345 - thanks for catching it, I'll edit as this is a pure C question.Sorbose
@Sorbose How come for the first malloc :x = malloc(dimension1_max * sizeof(int*));, you used sizeof(int*), when you used just sizeof(int) for the second malloc: temp = malloc(dimension1_max * dimension2_max * sizeof(int));...meaning the first has a * and the second doesn'tFrustrate
@Abdul: The first one allocates space for pointers to ints, the second one for the ints.Sorbose
@Sorbose If I was attempting to make a 2D array of char, would it be the same? The outer layer would be pointer to char and inner layer being char?Frustrate
Yes, that's correct, the outer layer points to the first element of the inner layer in each row/column.Sorbose
This does not allocate a 2D array, but an array of pointer plus arrays it points to. Addresing is very different, it has memory overhead and likely less efficient.Karate
How do you free the memory with the second option? (if you no longer have access to temp)Piccadilly
@laurent: If you no longer have access to temp, you can't free the memory. In the second option, x and temp are two completely independent memory regions and you'll have to free both using free. I'll edit the answer to show how to free here.Sorbose
@laurent, @schnaader, You'll certainly still have x, and therefore x[0] which is initialized with x[0] = temp, does free(x[0]) would free the whole array?Karrykarst
C
98

Since C99, C has 2D arrays with dynamical bounds. If you want to avoid that such beast are allocated on the stack (which you should), you can allocate them easily in one go as the following

double (*A)[n] = malloc(sizeof(double[n][n]));

and that's it. You can then easily use it as you are used for 2D arrays with something like A[i][j]. And don't forget that one at the end

free(A);

Randy Meyers wrote series of articles explaining variable length arrays (VLAs).

Canara answered 9/10, 2012 at 18:16 Comment(7)
Parts 2, 3, and 4 of Meyers's series are also available from Dr Dobbs.Chronister
Although this is a nice and elegant solution, the beauty is more in the declaration of A than in the actual allocation; in particular, no 2-D array is allocated anywhere, much less a dynamically sized one. The malloc just allocates a linear stretch of untyped memory suitably aligned for anything; the address A is initialized with could come from any source (well, because of aliasing issues I'd assume it could come from a one-dimensional array of char or double, or a "true" 2-dimensional array). The run-time sizeof just computes a number (always the right one, of course) ;-).Rondure
Great answer! Addition: In your example, both dimensions have the same size n. With different sizes, it would look like this: double (*a)[y] = malloc(sizeof(double[x][y]));. With three dimensions: double (*a)[y][z] = malloc(sizeof(double[x][y][z]));. And so on.Bourse
The only good answer to this question. However, I would always prefer using the pointer variable in the sizeof() like this: double (*A)[n] = malloc(n*sizeof(*A)); The advantage is, that you cannot get the type details wrong in the sizeof(), and it is always the same form of arrayLength*sizeof(*newPointerVar) as the malloc() argument. I believe, this form has the lowest opportunity for bugs to creep in.Barnet
@cmaster-reinstatemonica is this a VLA? It warns with -WvlaSpringwood
@Springwood It's an array type with a runtime length, so I guess you can call it VLA. However, it's nonsense to warn about it. Afaik, the warning was introduced to stop people from allocating potentially large VLAs on the stack as in double A[n][n];, which does run the risk of crashing the process due to excessive stack usage. Even today, stacks cannot grow indefinitely fast. However, a VLA on the heap is perfectly fine and much less error prone than trying to perform the index calculation yourself. So, warn about large stack object: Ok. Warn about VLA in general: Not Ok, imho.Barnet
@Springwood The C standard calls this variably modified type (VM), which are VLA and pointers to VLA. Warning about this is excessive, this warning should only concern VLA if defined as local variables (where some say that they could lead to stackoverflow). But even that is a not so useful assesment as such. VLA as local variables may even save you stack space if you otherwise would allocate a huge fixed sized array.Canara
S
82

With dynamic allocation, using malloc:

int** x;

x = malloc(dimension1_max * sizeof(*x));
for (int i = 0; i < dimension1_max; i++) {
  x[i] = malloc(dimension2_max * sizeof(x[0]));
}

//Writing values
x[0..(dimension1_max-1)][0..(dimension2_max-1)] = Value; 
[...]

for (int i = 0; i < dimension1_max; i++) {
  free(x[i]);
}
free(x);

This allocates an 2D array of size dimension1_max * dimension2_max. So, for example, if you want a 640*480 array (f.e. pixels of an image), use dimension1_max = 640, dimension2_max = 480. You can then access the array using x[d1][d2] where d1 = 0..639, d2 = 0..479.

But a search on SO or Google also reveals other possibilities, for example in this SO question

Note that your array won't allocate a contiguous region of memory (640*480 bytes) in that case which could give problems with functions that assume this. So to get the array satisfy the condition, replace the malloc block above with this:

int** x;
int* temp;

x = malloc(dimension1_max * sizeof(*x));
temp = malloc(dimension1_max * dimension2_max * sizeof(x[0]));
for (int i = 0; i < dimension1_max; i++) {
  x[i] = temp + (i * dimension2_max);
}

[...]

free(temp);
free(x);
Sorbose answered 27/5, 2009 at 20:19 Comment(15)
This won't compile, you need to declare x as "int **", not "int[][]".Yarn
What does this exactly do? What is dimension1_max and dimension2_max? What does the first "for"?Netti
Thanks, Adam, I knew something was wrong although I already corrected the first sizeof to int* instead of int.Sorbose
It is not a multidimensional array - it is array of pointers to int, or array of arrays. To allocate memory for real 2D array you need to use malloc(dim1 * dim2 * sizeof(int)). If some function expects pointer to 2D array, like foo(int * bar[5][6]) and you pass your x, weird things will happen. See en.wikipedia.org/wiki/C_syntax#Multidimensional_arraysDrennan
Ah, I get what you mean. I've added another example where a contiguous region of memory is provided and also corrected the malloc calls that weren't casted to int*/int**.Sorbose
Why are you casting mallocAnya
@JayB: In pure C, you don't cast malloc, but in C++ it is required, see #606345 - thanks for catching it, I'll edit as this is a pure C question.Sorbose
@Sorbose How come for the first malloc :x = malloc(dimension1_max * sizeof(int*));, you used sizeof(int*), when you used just sizeof(int) for the second malloc: temp = malloc(dimension1_max * dimension2_max * sizeof(int));...meaning the first has a * and the second doesn'tFrustrate
@Abdul: The first one allocates space for pointers to ints, the second one for the ints.Sorbose
@Sorbose If I was attempting to make a 2D array of char, would it be the same? The outer layer would be pointer to char and inner layer being char?Frustrate
Yes, that's correct, the outer layer points to the first element of the inner layer in each row/column.Sorbose
This does not allocate a 2D array, but an array of pointer plus arrays it points to. Addresing is very different, it has memory overhead and likely less efficient.Karate
How do you free the memory with the second option? (if you no longer have access to temp)Piccadilly
@laurent: If you no longer have access to temp, you can't free the memory. In the second option, x and temp are two completely independent memory regions and you'll have to free both using free. I'll edit the answer to show how to free here.Sorbose
@laurent, @schnaader, You'll certainly still have x, and therefore x[0] which is initialized with x[0] = temp, does free(x[0]) would free the whole array?Karrykarst
P
53

Basics

Arrays in c are declared and accessed using the [] operator. So that

int ary1[5];

declares an array of 5 integers. Elements are numbered from zero so ary1[0] is the first element, and ary1[4] is the last element. Note1: There is no default initialization, so the memory occupied by the array may initially contain anything. Note2: ary1[5] accesses memory in an undefined state (which may not even be accessible to you), so don't do it!

Multi-dimensional arrays are implemented as an array of arrays (of arrays (of ... ) ). So

float ary2[3][5];

declares an array of 3 one-dimensional arrays of 5 floating point numbers each. Now ary2[0][0] is the first element of the first array, ary2[0][4] is the last element of the first array, and ary2[2][4] is the last element of the last array. The '89 standard requires this data to be contiguous (sec. A8.6.2 on page 216 of my K&R 2nd. ed.) but seems to be agnostic on padding.

Trying to go dynamic in more than one dimension

If you don't know the size of the array at compile time, you'll want to dynamically allocate the array. It is tempting to try

double *buf3;
buf3 = malloc(3*5*sizeof(double));
/* error checking goes here */

which should work if the compiler does not pad the allocation (stick extra space between the one-dimensional arrays). It might be safer to go with:

double *buf4;
buf4 = malloc(sizeof(double[3][5]));
/* error checking */

but either way the trick comes at dereferencing time. You can't write buf[i][j] because buf has the wrong type. Nor can you use

double **hdl4 = (double**)buf;
hdl4[2][3] = 0; /* Wrong! */

because the compiler expects hdl4 to be the address of an address of a double. Nor can you use double incomplete_ary4[][]; because this is an error;

So what can you do?

  • Do the row and column arithmetic yourself
  • Allocate and do the work in a function
  • Use an array of pointers (the mechanism qrdl is talking about)

Do the math yourself

Simply compute memory offset to each element like this:

  for (i=0; i<3; ++i){
     for(j=0; j<3; ++j){
        buf3[i * 5 + j] = someValue(i,j); /* Don't need to worry about 
                                             padding in this case */
     }
  }

Allocate and do the work in a function

Define a function that takes the needed size as an argument and proceed as normal

void dary(int x, int y){
  double ary4[x][y];
  ary4[2][3] = 5;
}

Of course, in this case ary4 is a local variable and you can not return it: all the work with the array must be done in the function you call of in functions that it calls.

An array of pointers

Consider this:

double **hdl5 = malloc(3*sizeof(double*));
/* Error checking */
for (i=0; i<3; ++i){
   hdl5[i] = malloc(5*sizeof(double))
   /* Error checking */
}

Now hdl5 points to an array of pointers each of which points to an array of doubles. The cool bit is that you can use the two-dimensional array notation to access this structure---hdl5[0][2] gets the middle element of the first row---but this is none-the-less a different kind of object than a two-dimensional array declared by double ary[3][5];.

This structure is more flexible then a two dimensional array (because the rows need not be the same length), but accessing it will generally be slower and it requires more memory (you need a place to hold the intermediate pointers).

Note that since I haven't setup any guards you'll have to keep track of the size of all the arrays yourself.

Arithmetic

c provides no support for vector, matrix or tensor math, you'll have to implement it yourself, or bring in a library.

Multiplication by a scaler and addition and subtraction of arrays of the same rank are easy: just loop over the elements and perform the operation as you go. Inner products are similarly straight forward.

Outer products mean more loops.

Perseid answered 27/5, 2009 at 21:34 Comment(6)
Just one point - array of arrays is array of pointers, not a real multidimensional array. en.wikipedia.org/wiki/C_syntax#Multidimensional_arraysDrennan
@qrdl: As I read the standard int **a1; and int *a2[i]; int a3[n][m]; are different critters with different semantics. The last one gets allocated a block of continguous (possibly padded) memory without a separate set of intermediate pointers... Try printf("%d\n",sizeof(int[3][5])/sizeof(int));Perseid
Does "allocate in a function" work? How would the compiler know how much memory to allocate in the stack for the array ary4 if x and y are unkown?Spat
@MIkhail Er ... x and y are known at run time, and the compiler is not restricted to sizing the stack frame all in one go. Try it. Of course you can't return that array, you have to do all the work in the function, so my text is a bit misleading. I'll edit to fix that up.Perseid
I had this notion that the compiler needed to know the size of the array at compile time to allocate memory for it. I just tested this out and checked the generated assembly. Like you said, it sizes the stack frame depending on the input arguments. Thank you!Spat
@MIkhail For what it's worth, I think the compiler does need to know the size of globally scoped arrays at compile time.Perseid
D
14

If you know the number of columns at compile time, it's pretty simple:

#define COLS ...
...
size_t rows;
// get number of rows
T (*ap)[COLS] = malloc(sizeof *ap * rows); // ap is a *pointer to an array* of T

You can treat ap like any 2D array:

ap[i][j] = x;

When you're done you deallocate it as

free(ap);

If you don't know the number of columns at compile time, but you're working with a C99 compiler or a C2011 compiler that supports variable-length arrays, it's still pretty simple:

size_t rows;
size_t cols;
// get rows and cols
T (*ap)[cols] = malloc(sizeof *ap * rows);
...
ap[i][j] = x;
...
free(ap);

If you don't know the number of columns at compile time and you're working with a version of C that doesn't support variable-length arrays, then you'll need to do something different. If you need all of the elements to be allocated in a contiguous chunk (like a regular array), then you can allocate the memory as a 1D array, and compute a 1D offset:

size_t rows, cols;
// get rows and columns
T *ap = malloc(sizeof *ap * rows * cols);
...
ap[i * rows + j] = x;
...
free(ap);

If you don't need the memory to be contiguous, you can follow a two-step allocation method:

size_t rows, cols;
// get rows and cols
T **ap = malloc(sizeof *ap * rows);
if (ap)
{
  size_t i = 0;
  for (i = 0; i < cols; i++)
  {
    ap[i] = malloc(sizeof *ap[i] * cols);
  }
}

ap[i][j] = x;

Since allocation was a two-step process, deallocation also needs to be a two-step process:

for (i = 0; i < cols; i++)
  free(ap[i]);
free(ap);
Durra answered 9/10, 2012 at 18:19 Comment(3)
It would be nice if the downvoter would tell me what they think I got wrong.Durra
Please tell - what is ap and what is T?Ramin
@AquariusTheGirl: T represents any type (int, double, struct foo, etc.). ap is just an arbitrary variable name.Durra
P
0

Here is working code that defines a subroutine make_3d_array to allocate a multidimensional 3D array with N1, N2 and N3 elements in each dimension, and then populates it with random numbers. You can use the notation A[i][j][k] to access its elements.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>


// Method to allocate a 2D array of floats
float*** make_3d_array(int nx, int ny, int nz) {
    float*** arr;
    int i,j;

    arr = (float ***) malloc(nx*sizeof(float**));

    for (i = 0; i < nx; i++) {
        arr[i] = (float **) malloc(ny*sizeof(float*));

        for(j = 0; j < ny; j++) {
            arr[i][j] = (float *) malloc(nz * sizeof(float));
        }
    }

    return arr;
} 



int main(int argc, char *argv[])
{
    int i, j, k;
    size_t N1=10,N2=20,N3=5;

    // allocates 3D array
    float ***ran = make_3d_array(N1, N2, N3);

    // initialize pseudo-random number generator
    srand(time(NULL)); 

    // populates the array with random numbers
    for (i = 0; i < N1; i++){
        for (j=0; j<N2; j++) {
            for (k=0; k<N3; k++) {
                ran[i][j][k] = ((float)rand()/(float)(RAND_MAX));
            }
        }
   }

    // prints values
    for (i=0; i<N1; i++) {
        for (j=0; j<N2; j++) {
            for (k=0; k<N3; k++) {
                printf("A[%d][%d][%d] = %f \n", i,j,k,ran[i][j][k]);
            }
        }
    }

    free(ran);
}
Pernod answered 31/7, 2017 at 21:18 Comment(1)
Errors: insteast nx and ny sizes in the loop of make_3d_array(), it has to be ny and nz. Also, the memories of ran[i][j] and ran[i] are not freed here which is very bad. @rodrigoEnthral
S
-1

malloc will do.

 int rows = 20;
 int cols = 20;
 int *array;

  array = malloc(rows * cols * sizeof(int));

Refer the below article for help:-

http://courses.cs.vt.edu/~cs2704/spring00/mcquain/Notes/4up/Managing2DArrays.pdf

Sidewheel answered 9/10, 2012 at 16:18 Comment(5)
The question asks for C. That was C++.Cosmo
@rahul I would suggest that if you make the edit you did above that you leave the article you had linked. Despite the syntax being specific to C++, all of the concepts introduced there would hold for the method you propose in your edited answerPulitzer
@AK4749:- I deleted it as I was afraid to loose my points. I had lost many of them earlier also as people here are very specific to there questions and do not want to explore.!!! Anyways Thanx a lot for boosting meSidewheel
@RahulTripathi yes, i've noticed that "analness" about SO as well, but in its defense, it isn't a forum but rather a place to get specific questions answered. A different vibe to get used to i guess :/Pulitzer
You don't show how to access elements of the array once it is allocated. It's not as simple as array[i][j] using the variables you've defined.Probably
C
-1

There's no way to allocate the whole thing in one go. Instead, create an array of pointers, then, for each pointer, create the memory for it. For example:

int** array;
array = (int**)malloc(sizeof(int*) * 50);
for(int i = 0; i < 50; i++)
    array[i] = (int*)malloc(sizeof(int) * 50);

Of course, you can also declare the array as int* array[50] and skip the first malloc, but the second set is needed in order to dynamically allocate the required storage.

It is possible to hack a way to allocate it in a single step, but it would require a custom lookup function, but writing that in such a way that it will always work can be annoying. An example could be L(arr,x,y,max_x) arr[(y)*(max_x) + (x)], then malloc a block of 50*50 ints or whatever and access using that L macro, e.g.

#define L(arr,x,y,max_x) arr[(y)*(max_x) + (x)]

int dim_x = 50;
int dim_y = 50;

int* array = malloc(dim_x*dim_y*sizeof(int));

int foo = L(array, 4, 6, dim_x);

But that's much nastier unless you know the effects of what you're doing with the preprocessor macro.

Cosmo answered 9/10, 2012 at 16:22 Comment(1)
wrong, much too complicated, modern C can do better, please see my answer.Canara
G
-2
int rows, columns;
/* initialize rows and columns to the desired value */

    arr = (int**)malloc(rows*sizeof(int*));
        for(i=0;i<rows;i++)
        {
            arr[i] = (int*)malloc(cols*sizeof(int));
        }
Glower answered 9/10, 2012 at 16:47 Comment(1)
too complicated, modern C can do real multi-dimensional arrays in one goCanara
S
-3

// use new instead of malloc as using malloc leads to memory leaks `enter code here

    int **adj_list = new int*[rowsize];       
    for(int i = 0; i < rowsize; ++i)    
    {

        adj_list[i] = new int[colsize];

    }
Sherly answered 14/7, 2019 at 5:56 Comment(3)
This code is a syntax error in C, maybe you mistake it for some other languageMede
Modern C can handle multi-dimensional initialisations in one goHighstepper
This is C++. The question is about C.Springwood

© 2022 - 2024 — McMap. All rights reserved.