5

What is the best way to return a multidimensional array from a function in c ?

Say we need to generate a multidimensional array in a function and call it in main, is it best to wrap it in a struct or just return a pointer to memory on the heap ?

 int *create_array(int rows, int columns){
     int array[rows][columns] = {0};
     return array;
 }

 int main(){

     int row = 10;
     int columns = 2;
     create_array(row,columns); 
 }

The code above, is just to sketch out the basic program I have in mind.

gsamaras
  • 71,951
  • 46
  • 188
  • 305
NiallJG
  • 1,881
  • 19
  • 22
  • 5
    There are a lot of questions on this topic on SO. Which ones have you looked at and failed to understand the answers to? Note [Return pointer to local variable](http://stackoverflow.com/questions/4570366/pointer-to-local-variable) and don't do it. – Jonathan Leffler Jul 30 '17 at 07:33
  • 1
    I hope for your sake your sketch-up doesn't eventually lead to in-practice code, as no good will come from returning the base address of an automatic array whose lifetime expires the moment the containing function returns. – WhozCraig Jul 30 '17 at 07:36
  • Yes, I've seen the compiler warnings, mainly I've seen this handled with either a struct or a pointer to a pointer and I'd just like to know what the canonical approach is – NiallJG Jul 30 '17 at 07:40
  • It would help you to learn the difference between the [heap and the stack](http://gribblelab.org/CBootCamp/7_Memory_Stack_vs_Heap.html) – Mawg says reinstate Monica Jul 30 '17 at 08:35
  • Structs can be allocated on the heap as well – NiallJG Jul 30 '17 at 08:39

4 Answers4

7

This is wrong:

int *create_array(int rows, int columns){
     int array[rows][columns] = {0};
     return array;
}

and should produce a warning like this:

prog.c:2:6: note: (near initialization for 'array')
prog.c:3:13: warning: return from incompatible pointer type [-Wincompatible-pointer-types]
      return array;
             ^~~~~
prog.c:3:13: warning: function returns address of local variable [-Wreturn-local-addr]

since you are returning the address of an automatic variable; its lifetime ends when its corresponding function terminates.


You should either declare a double pointer in main(), pass it through the function, dynamically allocate memory for it and return that pointer. Or you could create the array in main() and pass the double pointer to the function.


I want to know ways to allocate multidimensional arrays on the heap and pass them around

For allocating memory on the heap you could use one of these two methods, which involve pointers:

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

// We return the pointer
int **get(int N, int M) /* Allocate the array */
{
    /* Check if allocation succeeded. (check for NULL pointer) */
    int i, **array;
    array = malloc(N*sizeof(int *));
    for(i = 0 ; i < N ; i++)
        array[i] = malloc( M*sizeof(int) );
    return array;
}

// We don't return the pointer
void getNoReturn(int*** array, int N, int M) {
    /* Check if allocation succeeded. (check for NULL pointer) */
    int i;
    *array = malloc(N*sizeof(int *));
    for(i = 0 ; i < N ; i++)
        (*array)[i] = malloc( M*sizeof(int) );
}

void fill(int** p, int N, int M) {
    int i, j;
    for(i = 0 ; i < N ; i++)
        for(j = 0 ; j < M ; j++)
            p[i][j] = j;
}

void print(int** p, int N, int M) {
    int i, j;
    for(i = 0 ; i < N ; i++)
        for(j = 0 ; j < M ; j++)
            printf("array[%d][%d] = %d\n", i, j, p[i][j]);
}

void freeArray(int** p, int N) {
    int i;
    for(i = 0 ; i < N ; i++)
        free(p[i]);
    free(p);
}

int main(void)
{
    int **p;
    //getNoReturn(&p, 2, 5);
    p = get(2, 5);
    fill(p ,2, 5);
    print(p, 2, 5);
    freeArray(p ,2);
    return 0;
}

Pick whichever suits best your style.

gsamaras
  • 71,951
  • 46
  • 188
  • 305
  • yes, that's why I want to know the best way to allocate multidimensional arrays on the heap and pass them around – NiallJG Jul 30 '17 at 07:42
  • `*table[i]` should be `(*table)[i]`. – mch Jul 30 '17 at 07:49
  • `void getNoReturn(int*** table, int N, int M)`: please don't be 3 star programmer, just return the pointer. – chqrlie Jul 30 '17 at 08:21
  • I see no 2D-array here, but just 1D-arrays, in fact 1 1D-`int`-pointer array and N 1D-`int`-arrays. – alk Jul 30 '17 at 09:50
  • @alk you mean the function name, right? Modified, is it OK now? chqrlie I am not, I just wanted to present a different approach...Do you want me to update my answer with a hint to return the pointer? – gsamaras Jul 30 '17 at 11:28
3

What is the best way to return a multidimensional array from a function in c ?

My recommendation is to avoid doing that, and avoid multidimensional arrays in C (they are unreadable and troublesome).

I would recommend making your matrix type your proper abstract data type, represented by some struct ending with a flexible array member:

struct mymatrix_st {
  unsigned nbrows, nbcolumns;
  int values[];
};

Here is the creation function (returning a properly initialized pointer to dynamic memory):

struct mymatrix_st*
create_matrix(unsigned mnbrows, unsigned mnbcolumns) {
  if (mnbrows > UINT_MAX/4 || mnbcolumns > UINT_MAX/4
      ||(unsigned long)mnbrows * (unsigned long)mnbcolums
        > UINT_MAX) {
   fprintf(stderr, "too big matrix\n");
   exit(EXIT_FAILURE);
 };
 size_t sz = sizeof(struct mymatrix_st)+(mnbrows*mnbcolumns*sizeof(int));
 struct mymatrix_st*m = malloc(sz);
 if (!m) { 
   perror("malloc mymatrix"); exit(EXIT_FAILURE); };
 m->nbrows = mnbrows;
 m->nbcolumns = mnbcolumns;
 for (unsigned long ix=(unsigned long)mnbrows * (unsigned long)mnbcolumns-1;
      ix>=0; ix--)
   m->values[ix] = 0;
 return m;;
} /*end create_matrix*/

It is on purpose that struct mymatrix_st don't contain any interior pointer. You can and should use free to destroy it.

Here is the accessor function; make it a static inline function and define it in the same header declaring struct mymatrix_st and create_matrix, e.g.

static inline int getmatrix(struct mymatrix_st*m, unsigned row, unsigned col) {
  if (!m) {
     fprintf(stderr, "getmatrix with no matrix\n");
     exit(EXIT_FAILURE);
  };
  if (row >= m->nbrows || col >= m->nbcolumns){
     fprintf(stderr, "getmatrix out of bounds\n");
     exit(EXIT_FAILURE);
  };
  return m->values[row*m->nbcolumns + col];
}

I leave up to you to define and implement the other operations on your abstract struct mymatrix_st type.

(you could adapt the code, perhaps removing the out of bound check, but I don't recommend unsafe code)

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • 1
    IMO, much better than than the accepted multi-malloc version. (Although I'd remove the `m` null check and only `assert` the bounds so the checks can be optimized out when NDEBUG is on, because I like code that's fast and slightly dangerous) :D. – Petr Skocik Jul 30 '17 at 08:53
0
int** create_array(int rows, int columns){
    int** array = malloc(rows * sizeof(int*));
    int i;
    for (i=0; i<rows; i++)
        array[i] = malloc(columns * sizeof(int));
    return array;
}

should do the trick. If you use int array[rows][columns]; then it's dead as soon as the functiom returns, and you get a UB. You should at least use dynamic memory allocation.

iBug
  • 35,554
  • 7
  • 89
  • 134
  • I see no 2D-array here, but just 1D-arrays, in fact 1 1D-`int`-pointer array and N 1D-`int`-arrays. – alk Jul 30 '17 at 09:51
0

You can't return an array, but you can return a regular pointer and document that the callee may treat it as a pointer to a multidimensional array of the dimensions that it had passed to the caller.

(Note that the returned pointer must point to dynamic or static, but not automatic memory--don't return pointers to local variables!)

It takes some slightly wordy casts and possibly a macro but it's doable:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
void* 
multi(int R, int C)
{
    return calloc ( 1, sizeof(int[R][C])  ); //or sizeof(int)*R*C
}


int main()
{
    int (*r_)[3][4] = multi(3,4);
    if(!r_) return EXIT_FAILURE;    

#define r (*r_)
    //emulate C++ a reference  -- r now behaves as an `int r[3][4];`

    //Test that addresses advance as they would in a multi-d array

    int local[3][4];
    assert(&local[1][0]-&local[0][0] == 4); //base example
    assert(&r[1][0]-&r[0][0] == 4);         //"returned" multi-d array

    free(r); //or free(&r) or free(r_) -- here it shouldn't matter


#undef r

    return 0;

}

Note that an array of pointers is not the same thing as a multi-d array. A true multi-d array is one contiguous block, whereas an array of pointers (though usable with the same indexing syntax) has much worse locality of reference, so this might be preferable over returning pointers to pointers if you want better performance.

Petr Skocik
  • 58,047
  • 6
  • 95
  • 142
  • 1
    Notice that [VLA](https://en.wikipedia.org/wiki/Variable-length_array) are a recent feature of C, and that `sizeof` on `VLA` is the only case where it is not a compile time constant. I won't recommend using it here in the call to `calloc`, but simply `calloc(R*C,sizeof(int))` – Basile Starynkevitch Jul 30 '17 at 08:16
  • Why not make `multi` return a `void*` and simply write: `int (*r)[][4] = multi(3,4); if(!r) return EXIT_FAILURE;` – chqrlie Jul 30 '17 at 08:24