How to return a 2D array from a function in C?
Asked Answered
P

6

33

I am a Ruby programmer who has ended up developing a code generate for C. Its like asking a Limo to tow a 1960s truck. Any way.

Here is what I thought should work but doesnt work.

float[][] pixels()
{
  float x[][]= { {1,1},{2,2} };
  return x
}

void drawLine(float x[][2])
{
  //drawing the line
}

//inside main
drawLine(pixels());

I have banged my head on my desk trying to get this thing work. Please help.

Pumpernickel answered 5/3, 2011 at 4:36 Comment(3)
This might interest you https://mcmap.net/q/281818/-how-to-access-a-local-variable-from-a-different-function-using-pointersUxoricide
what seems to be the problem?Chaussure
@SOE Read Mahesh's answer to understand what is the problem.Pumpernickel
P
8

Thank you all for your answers and more specifically for the detailed explanation of the array-pointer relationship.

I encapsulated the array in a structure

 struct point_group1 {
        float x[3];
        float y[3];
};

struct point_group1 pixels(){
    struct point_group1 temp;

    temp.x[0] = 0.0;
    temp.x[1] = 1.0;
    temp.x[2] = -1.0;

    temp.y[0] = 0.0;
    temp.y[1] = 1.0;
    temp.y[2] = 1.0;

    return temp;    
}



struct point_group1 points1  = pixels();
axPoly(points1.x, points1.y ,3, 0.0);
Pumpernickel answered 5/3, 2011 at 7:26 Comment(1)
This is a pretty good solution, just be aware that if these are large arrays and your compiler isn't good at optimizing you may end up creating needless copies.Poulter
A
39

In C, pointers and arrays are closely related. Also, you usually need to pass the size of an array as a separate variable. Let's start you with:

#include <stdio.h>

float** createArray(int m, int n)
{
    float* values = calloc(m*n, sizeof(float));
    float** rows = malloc(m*sizeof(float*));
    for (int i=0; i<m; ++i)
    {
        rows[i] = values + i*n;
    }
    return rows;
}

void destroyArray(float** arr)
{
    free(*arr);
    free(arr);
}

void drawLine(const float** coords, int m, int n);

int main(void)
{
    float** arr = createArray(2,2);
    arr[0][0] = 1;
    arr[0][1] = 1;
    arr[1][0] = 2;
    arr[1][1] = 2;
    drawLine(arr, 2, 2); 
    destroyArray(arr);
}
Allegedly answered 5/3, 2011 at 4:52 Comment(3)
I just took a try for this code-snippetand got: "error: invalid conversion from ‘void*’ to ‘float*’" in createArray.You need to add a cast: "float* values = (float*)calloc(mn, sizeof(float));" .. same for the float*Dehart
@Dehart Are you possibly compiling it as though it were C++ code? It is valid C and invalid C++.Allegedly
so is float** the "type" for 2D float arrays?Colo
P
8

Thank you all for your answers and more specifically for the detailed explanation of the array-pointer relationship.

I encapsulated the array in a structure

 struct point_group1 {
        float x[3];
        float y[3];
};

struct point_group1 pixels(){
    struct point_group1 temp;

    temp.x[0] = 0.0;
    temp.x[1] = 1.0;
    temp.x[2] = -1.0;

    temp.y[0] = 0.0;
    temp.y[1] = 1.0;
    temp.y[2] = 1.0;

    return temp;    
}



struct point_group1 points1  = pixels();
axPoly(points1.x, points1.y ,3, 0.0);
Pumpernickel answered 5/3, 2011 at 7:26 Comment(1)
This is a pretty good solution, just be aware that if these are large arrays and your compiler isn't good at optimizing you may end up creating needless copies.Poulter
L
7
float (*pixels(void))[2] 
{
  static float x[2][2]= { {1,1},{2,2} };
  return x;
}

void drawLine(float (*x)[2])
{
  //drawing the line
  //x[0][0];
}

//inside main
drawLine(pixels());
Lenhard answered 25/4, 2016 at 6:43 Comment(1)
You have the only correct answer here! Too bad you did not explain anything. If you have added some explanation, feel free to drop a comment to me, I'll check and upvote if I think it is good.Estivate
B
6

In C/C++, when you pass an array to a function, it decays to be a pointer pointing to first element of the array. So, in pixels() function, you are returning the address of a stack allocated variable. The returning variable's address is no longer valid because on pixels() return, the stack allocated variable goes out of scope. So, instead you should for a variable whose storage is dynamic ( i.e., using malloc, calloc ).

So, for a two dimensional array, you may use float** arrayVariable;. Also, if you passing this to a function, you should be wary of how many rows & columns it has.

int rows, columns;

float** pixels()
{
    // take input for rows, columns
    // allocate memory from free store for the 2D array accordingly
    // return the array
}

void drawLine( float** returnedArrayVariable )
{
  //drawing the line
}

Since, 2D array is managing resources it self, it should return the resources back to the free store using free.

Bradford answered 5/3, 2011 at 5:13 Comment(1)
I guess you want me to use malloc inside the pixels function. Then I need to remember to free it as well.Pumpernickel
P
1

The easiest way is probably going to be declaring the float array in main and having pixels just fill it:

#define PIXEL_X_SIZE 2
#define PIXEL_Y_SIZE 2

int pixels(float x[][PIXEL_X_SIZE], int len) {
    /* I don't know if you want the logic of this method to ever change,
       but this will be roughly equivalent to what you do above */
    if (len < PIXEL_Y_SIZE) {
        /* the length of the passed array is too small, abort */
        return -1;
    }

    x[0][0] = x[0][1] = 1;
    x[1][0] = x[1][1] = 2;
    return 0;
}

void drawLine(float x[][PIXEL_X_SIZE]) {
    /* this will work fine */
}

int main() {
    float pixel_array[PIXEL_Y_SIZE][PIXEL_X_SIZE];
    pixels(pixel_array, PIXEL_Y_SIZE);
    drawLine(pixel_array);
}

You can also use malloc and free and store your pixels on the heap, but if this is all the bigger the pixels array is going to be, there's really no need and it just adds additional complexity to make sure your memory always get properly allocated and freed.

Poulter answered 5/3, 2011 at 4:49 Comment(2)
You approach is fine. The only concern I feel is that since our application is expected to run on an embedded device the code review check-list asks to minimise the global vars. The code will have hundreds of pixel like functions.Pumpernickel
@Akshar I don't use any global vars... I #define a couple things, but if you really wanted to you could just repeat the 2s everywhere, but you're setting yourself up for a big maintenance headache any time any of those values change and/or very hard to debug bugs if somebody gets a new size wrong.Poulter
M
0

I'm surprised no one has answered the question properly yet.

The simple reason your example doesn't compile is that it is illegal to return arrays from a function.

The following combinations are illegal in C:

[]() - cannot have an array of functions
()() - cannot have a function that returns a function
()[] - cannot have a function that returns an array

So you have to return a pointer to an array:

float (*pixels(void))[2] 
{
  static float x[2][2]= { {1,1},{2,2} };
  return x;
}

Which reads as "pixels is a function that takes void and returns a pointer to an array 2 of float."

Eunice provided a similar answer (function returning a pointer to an array) but did not elaborate. Basically, an array not used in a sizeof or &/* operation is decayed by the compiler into a pointer. And this decay is NOT recursive- it only takes place once. So even though in this example x is a float [][], it decays into float (*)[], or "a pointer to array of float". Note that you will have to declare your array at the call site with the same type:

float (*y)[2] = pixels();

If your array size is dynamic, you also want to allocate the memory within the function in order for it to survive into the calling scope. Here is that version (using ** instead of (*)[] for brevity):

float **pixels(int nrows, int ncolumns)
 {
   // allocate the array of rows
   float **array = malloc(nrows * sizeof(float *));

   // iterate through the rows and malloc the appropriate size for the columns
   for(int i = 0; i < nrows; i++) {
      array[i] = malloc(ncolumns * sizeof(float));
   }
   return array;
 }

To keep the multi-dim array contiguous in memory, you will want to implement the following:

// allocate a "mini array" nrow pointers deep, each the size of a pointer
int **contig_array = malloc(nrows * sizeof(int *));

// at the first pointer, allocate enough for the entire flattened multi-dim array
contig_array[0] = malloc(nrows * ncolumns * sizeof(int));

// Finally, iterate through each row (pointer) in the first 'mini array'
// and set the pointer to point to the address of the
// column start of the flattened array
for(i = 1; i < nrows; i++)
    contig_array[i] = contig_array[0] + i * ncolumns;

To reinforce the concept of returning a pointer to an array, here is an example function taking a file name and returning an array of strings, using the "pointer to array" return type:

char (*file2array(char *filename))[]
{
    FILE *fptr = fopen(filename, "r");
    // determine lines in file first
    fseek(fptr, 0, SEEK_END);
    int filesize = ftell(fptr);
    fseek(fptr, 0, SEEK_SET);
    // malloc array
    int numlines = 100000;
    char (*newarray)[MAXCHARS+1] = malloc(sizeof *newarray * numlines);
    // fill array 
    int i = 0;
    while(fscanf(fptr, "%[^\n]%*c", newarray[i++]) != EOF);
    fclose(fptr);
    return newarray;
}
Morbihan answered 21/7 at 9:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.