Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Matrix transposition porting from Java to C, incompatible types issue

I have to port some Java methods in C, have a Java background but I'm a total noob in C programming

In java

float[][] traspose(float Xy[][]) {
    float result[][]=new float[5000][3000];
    for(int i = 0; i < m; i++) {
        for(int j = 0; j < n; j++) {
            result[i][j] = Xy[j][i];
        }
    }
    return result;
}

My C porting attempt

float traspose(int m, int n, float Xy[m][n]) {
    int i,j;
    float result[5000][3000];
    for(i = 0; i < m; i++) {
        for(j = 0; j < n; j++) {
            result[i][j] = Xy[j][i];
        }
    }
    return result;
}

This doesn't work and get incompatible types error.

My 2 questions

1) How should I fix my code? Googling I have seen some questions about returning matrix in C but not very clear and in most case was suggested to use an approach that doesn't imply the use of return.

2) I have seen that usually this kind of operations in C are written without return type approach e.g. void methods that operate on constants or the code is written directly in main. Why?

EDIT

Following the suggestions I have tried to code this

float **transpose(int m, int n, float Xy[]) {
    int i,j;
    float **result = allocate_mem_m(m,n);
    for(i = 0; i < m; i++) {
        for(j = 0; j < n; j++) {
            result[i][j] = Xy[j*n+i];
        }
    }
    return result;
}


int main(int argc, char **argv) {
    printf("Hello World!");
    float matrix[3][3] = {{0, 1, 2}, {3, 4, 5}, {6, 7, 8}};
    printf("Matrix created\n");
    int size=3;
    print(size, size, matrix);
    float **transposedmat = transpose(size, size, &matrix[0][0]);
    printMat(size, size, transposedmat);
    return 0;
}

But unfortunately the program crashes when I invoke trasposition method.

PS I want to compile the code in standard C (not in C99)

like image 618
AndreaF Avatar asked Jan 11 '23 13:01

AndreaF


2 Answers

Strictly speaking, the answers about using pointer to pointer to float are not technically correct, because a 2D array of floats is not the same as a pointer to pointer to float.

This would be the equivalent C code:

#include <stdlib.h>

float (*transpose(int m, int n, float Xy[m][n]))[5000][3000] {
    float (*result)[5000][3000] = malloc(sizeof(*result));
    for(int i = 0; i < m; i++) {
        for(int j = 0; j < n; j++) {
            (*result)[i][j] = Xy[j][i];
        }
    }
    return result;
}

This works with pointers: the first line inside the function allocates space for a 2D array of 5000 by 3000, makes the copy as you have in your Java code, and returns the pointer to the new array. Note that you have to free the memory once you are done with it (by calling free()).

The function returns a pointer to an array, which means you'll have to use it like this:

float (*array)[5000][3000] = transpose(m, n, Xy);

And then you can access element i,j from the new array using (*array)[i][j].

To free, you do this:

free(array);

Finally, remember to compile this with C99 support - you need it because of the variable length array Xy in the parameters list. If you're using gcc, this can be achieved with -std=c99

The solution proposed in the other answers may be enough for your purposes, but keep in mind that using a float ** as a 2D array has a few caveats and 'gotchas'. For example, with the float ** solution, you'll have to manually free every position in array[i] before freeing array itself; sizeof will not tell you the true size of the "simulated" 2D array, and floats will not be stored in memory contiguously.

2) I have seen that usually this kind of operations in C are written without return type approach e.g. void methods that operate on constants or the code is written directly in main. Why?

The part about code being written directly in main() is not very common. Maybe you just saw some tutorial examples. In bigger programs, and in general, this is, of course, not inside main().

Writing it without a return type can be useful if you don't want to have to allocate memory: you leave that up to the caller. This is important, because you don't place the burden of freeing memory you allocated on the caller. Instead, you receive a pointer to an already allocated memory buffer, given to you by the caller, and write the results in there. This is generally a superior approach when it comes to memory management, but of course, lots of design choices and little details can quickly change this.

UPDATE (how to compile it without C99 support):

Well, the problem here is that Xy may be a 2D array of arbitrary length, i.e., you want to call transpose() with any 2D array (that's why you're giving it m and n for the dimensions).

C has no direct way of passing arbitrarily sized 2D arrays to a function. Such support was added in C99. If you want to do this in C89, a known workaround is to use the fact that arrays are linearly layed out in contiguous memory positions, and thus use it as if it were a 1D array of m*n floats. In other words, you roll your own indexing. Since C arrays are stored in row-major order, Xy[i][j] is the same as Xy_flat[i*n+j]. So, in effect, transpose() receives a pointer to the first element in Xy, and treats Xy as a 1D array. We just have to replace Xy[i][j] with Xy[i*n+j]:

/* C89 version */
#include <stdlib.h>

float (*transpose2(int m, int n, float Xy[]))[5000][3000] {
    float (*result)[5000][3000] = malloc(sizeof(*result));
    int i, j;
    for(i = 0; i < m; i++) {
        for(j = 0; j < n; j++) {
            (*result)[i][j] = Xy[j*n+i];
        }
    }
    return result;
}

This may seem weird and non trivial for someone coming from Java, but C usually works at a lower level.

To use this function, you have to give it a pointer to the first element of Xy. Here's an example:

float matrix[3][3] = {{0, 1, 2}, {3, 4, 5}, {6, 7, 8}};
float (*transposed)[5000][3000] = transpose(3, 3, &matrix[0][0]);
/* Use (*transposed)[i][j]... */
free(transposed);

If you don't want to hang around with (*array)[5000][3000] all over the code because of the fixed hardcoded dimensions, you can certainly use the solutions in the other answers, but always keeping in mind the differences between a pointer to pointer to float and a 2D array of floats. Since you seem to prefer this approach, here's how the code would look like:

float **allocate_mem_m(int m, int n)
{
 int i;
 float **arr = malloc(n*sizeof(*arr));
 for(i=0;i<n;i++)
   {
     arr[i]=malloc(m*sizeof(**arr));
   }
 return arr;
} 


float **transpose(int m, int n, float Xy[]) {
  int i,j;
  float **result = allocate_mem_m(m,n);
  for(i = 0; i < m; i++) {
    for(j = 0; j < n; j++) {
      result[i][j] = Xy[j*n+i];
    }
  }
  return result;
}

I took the liberty to change your allocate_mem_m() to receive the dimensions only, and return the pointer with the allocated memory. I think it starts to get a little overly complex when you use float ***. That's not necessary.

As a word of advice, I would equally add a free_mem_m() to ease the process of freeing the allocated memory:

void free_mem_m(int m, float **array) {
  int i;
  for (i = 0; i < m; i++) {
    free(array[i]);
  }
  free(array);
}

Here's the full code listing:

#include <stdlib.h>

float **allocate_mem_m(int m, int n)
{
 int i;
 float **arr = malloc(n*sizeof(*arr));
 for(i=0;i<n;i++)
   {
     arr[i]=malloc(m*sizeof(**arr));
   }
 return arr;
} 

void free_mem_m(int m, float **array) {
  int i;
  for (i = 0; i < m; i++) {
    free(array[i]);
  }
  free(array);
}

float **transpose(int m, int n, float Xy[]) {
  int i,j;
  float **result = allocate_mem_m(m,n);
  for(i = 0; i < m; i++) {
    for(j = 0; j < n; j++) {
      result[i][j] = Xy[j*n+i];
    }
  }
  return result;
}

And an example usage:

int main(void) {
  float Xy[3][3] = { { 0, 1, 2 }, { 3, 4, 5 }, { 6, 7, 8 } };
  float **transposed = transpose(3, 3, &Xy[0][0]);
  int i, j;
  for (i = 0; i < 3; i++)
    for (j = 0; j < 3; j++)
      printf("%f ", transposed[i][j]);
  printf("\n");
  free_mem_m(3, transposed);
  return 0;
}

Note that transpose() assumes a square matrix (because you allocate a block of n positions for m floats). If you want to use this with rectangular matrixes, you would have to allocate m blocks to hold n floats instead.

Live demo working: http://ideone.com/CyNdpn

Final note: The hack to treat a 2D array as 1D array is old and tricky; it is generally considered bad practice (in my opinion), and you should avoid this kind of clever code. But if you don't want to use C99 features, this is all you're left with.

For interested readers, at the risk of being a bit of a spammer, I have written 2 articles in my blog about generalizing this idea to arbitrary arrays with N dimensions. It goes a little bit deeper in the technical explanation of why you need to do it, and how to do it: http://codinghighway.com/?p=1159 and http://codinghighway.com/?p=1206

like image 126
Filipe Gonçalves Avatar answered Jan 17 '23 14:01

Filipe Gonçalves


Read up on pointers in C. The array name result is the same thing as &result[0] (address of the first element of the result array) so when you return result, you are returning a pointer to the array.

However, you are declaring result on the stack of the traspose function. This local variable will be destroyed when the function returns. Even if result was returned as a pointer, it would not be a valid pointer because the memory is gone once the function returns.

Try using malloc to put the result array on the heap (dynamic memory), then return a pointer to that array. You will need to change the return type of the function to be a pointer, as well.

Edit for example: Create a pointer to a pointer to a float. For each element of that array, malloc the space for each float.

float **array;
array = malloc(rows * sizeof(float *));
for (i = 0; i < rows; i++)
{
  array[i] = malloc(cols * sizeof(float));
}

Fill the array with whatever you want from the original array.

for (i = 0; i < rows; i++) 
{
  for (j = 0; j < cols; j++)
  {
    array[i][j] = 0; //VALUE
  }
}
like image 32
aglasser Avatar answered Jan 17 '23 14:01

aglasser