0

Short: How can I pass a 2D array as a function argument and get the results stored in the argument address?

Longer: I have code (below) which calls, from main() an initialization function and then prints the content. However, I would expect that after storing the pointers given by malloc into the variable A the results would be saved in the argument but it does not. When doing similar things with 1D arrays it works smoothly but not for 2D arrays.

For simplicity, in this case I make it 3x3, but I want to have a non-fixed amount of GB of data. Then, I must use malloc and I cannot declare the matrix size at compilation time.

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

void print_matrix(int n, double **A) {
    int i, j;
    for(i=0; i<n; i++) {
        for(j=0;j<n;j++) {
            printf("%0.2f ",A[i][j]);
        }
        printf("\n");
    }
}

void init(int n, double **A) {
    int i;
    A = (double **)malloc(n * n * sizeof(double *)); 
    for(i=0; i<n; i++) {
         A[i] = (double *)malloc(n * sizeof(double)); 
    }
    A[0][0] = 9.0;
    A[0][1] = 6.0;
    A[0][2] = -3.0;
    A[1][0] = 6.0;
    A[1][1] = 13.0;
    A[1][2] = -5.0;
    A[2][0] = -3.0;
    A[2][1] = -5.0;
    A[2][2] = 18.0;
    print_matrix(n, A);
}

int main() {
    int i;
    int n=3;
    double **A;
    init(n, A);
    print_matrix(n, A);
    for(i=0; i<n; i++)
        free(A[i]);
    free(A);
    return 0;
}

After compiling I can print the content of the matrix inside the init() function but not from main() without getting a segmentation fault, which of course means that A is pointing to unassigned memory and that not even A[0][0] is pointing to the root of the matrix. I am using GCC and standard C99.

$ gcc --version
gcc (Ubuntu 9.2.1-9ubuntu2) 9.2.1 20191008
$ gcc main.c -o main -std=c99
$ ./main
9.00 6.00 -3.00 
6.00 13.00 -5.00 
-3.00 -5.00 18.00 
Segmentation fault (core dumped)

There are other useful questions about passing 2D arrays but they do not solve my point because they do not tackle the issue of getting the results back in the argument.

Edit: This is a simple case, but I would need to pass several matrices as function arguments. Therefore, I cannot simply return the address of the matrix A, because I would need to save the addresses of several matrices.

albertgumi
  • 322
  • 1
  • 4
  • 13

2 Answers2

1

I'd do it like

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

void print_matrix(int n, double **A) {
    int i, j;
    for(i=0; i<n; i++) {
        for(j=0;j<n;j++) {
            printf("%0.2f ",A[i][j]);
        }
        printf("\n");
    }
}

double ** init(int n) {
    int i;
    double **A;
    A = (double **)malloc(n * n * sizeof(double *));
    for(i=0; i<n; i++) {
         A[i] = (double *)malloc(n * sizeof(double));
    }
    A[0][0] = 9.0;
    A[0][1] = 6.0;
    A[0][2] = -3.0;
    A[1][0] = 6.0;
    A[1][1] = 13.0;
    A[1][2] = -5.0;
    A[2][0] = -3.0;
    A[2][1] = -5.0;
    A[2][2] = 18.0;
    print_matrix(n, A);
    return A;
}

int main() {
    int i;
    int n=3;
    double **A;
    A = init(n);
    print_matrix(n, A);
    for(i=0; i<n; i++)
        free(A[i]);
    free(A);
    return 0;
}

Allocate memory within your init() function and return the pointer.

[ronald@ocelot tmp]$ ./x
9.00 6.00 -3.00 
6.00 13.00 -5.00 
-3.00 -5.00 18.00 
9.00 6.00 -3.00 
6.00 13.00 -5.00 
-3.00 -5.00 18.00 

An answer to the original question would be (only the changed code)

void init(int n, double ***A) {
    int i;
    *A = (double **)malloc(n * n * sizeof(double *));
    for(i=0; i<n; i++) {
         (*A)[i] = (double *)malloc(n * sizeof(double));
    }
    (*A)[0][0] = 9.0;
    (*A)[0][1] = 6.0;
    (*A)[0][2] = -3.0;
    (*A)[1][0] = 6.0;
    (*A)[1][1] = 13.0;
    (*A)[1][2] = -5.0;
    (*A)[2][0] = -3.0;
    (*A)[2][1] = -5.0;
    (*A)[2][2] = 18.0;
    print_matrix(n, *A);
}

int main() {
    int i;
    int n=3;
    double **A;
    init(n, &A);
    print_matrix(n, A);
    for(i=0; i<n; i++)
        free(A[i]);
    free(A);
    return 0;
}
Ronald
  • 2,842
  • 16
  • 16
  • Thank you for your quick answer, however it does not solve my problem because it does not tackle what I need. I have added an "Edit" section at the end of the question to clarify my situation: I would need to save information of several matrices. – albertgumi Apr 19 '20 at 10:00
  • You can initialise as many matrices as you like with either code. And because the dereferenced A is hard to read, I'd still choose for the first solution. `double **A1 = init(n); double **A2 = init(n);` works just as well as `double **A1, **A2; init(n, &A1); init(n, &A2);` – Ronald Apr 19 '20 at 10:15
0

Pass the matrix by a pointer. Because no one wants to be a three star programmer, create a structure for your data and pass a pointer to the structure. Remember that readability of the code, proper encapsulation and readable object-oriented programing will take you further. Remember about error handling, which I hope you omitted in your question for brevity. Do not cast result of malloc. Use size_t type to represent count of elements in an array.

That said, I would do:

// Represents a n x n dynamically allocated matrix.
struct matrix_s {
   double **a;
   size_t n;
};

// Forward declaration for `matrix_init`.
void matrix_free(struct matrix_s *m);

int matrix_init(struct matrix_s *m, size_t n) {
    assert(n > 2); // as you seem to access indices at least 2
    m->a = calloc(n * n, sizeof(*m->a));
    if (m->a == NULL) {
        goto ERROR;
    }
    m->n = 0;
    for(size_t i = 0; i < n; i++) {
         m->a[i] = calloc(n, sizeof(*A[i])); 
         m->n++;
         if (m->a[i] == NULL) {
            goto ERROR;
         }
    }

    // fill the array

    // SUCCESS
    return 0;

    ERROR:
    matrix_free(m);
    return ENOMEM;
}

void matrix_print(struct matrix_s *m) {
   for (size_t i = 0; i < m->n; ++i) {
       printf(....);
   }
}

void matrix_free(struct matrix_s *m) {
   for (size_t i = 0; i < m->n; ++i) {
      free(m->a[i]);
   }
   free(m->a);
   m->a = NULL;
   m->n = 0;
}

int main() {
    struct matrix_s m;
    if (matrix_init(&m, 3) != 0) {
       abort();
    }
    matrix_print(&m);
    matrix_free(&m);
    return 0;
}

Still, if you want to, you can be a 3-star programmer and pass the pointer A from main by a pointer with almost virtually no changes:

void init(int n, double ***A0) {
    double **A = malloc(n * n * sizeof(*A)); 

    // same your code without any modification

    *A0 = A; // asing the outer pointer to A
}

int main() {
    double **A;
    init(3, &A); // pass `A` by a pointer
    // ...
}

Because your matrix seems to have equal row size for each row, with proper encapsulation you could think about just allocating a single memory block for n * n doubles and calculate the indices yourself to navigate to. You could also use a variable length array like double (*array)[n] to access your data with array-like seamless fashion. But resizing the array could get significantly harder.

struct matrix_s {
    double *a;
    size_t n;
};

double *matrix_getp(struct matrix_s *m, size_t i, size_t j) {
     return &m->a[i * m->n + j];
}

int matrix_init(struct matrix_s *m, size_t n) {
     m->a = malloc(n * n * sizeof(*m->a)); // only a single allocation
     // ...

     // access to matrix at A[0][0]  
     *matrix_getp(m, 0, 0) = 1;
     // or the same using a VLA variable:
     double (*a)[n] = (void*)m->a;
     a[0][0] = 1;

     // ...
}
KamilCuk
  • 120,984
  • 8
  • 59
  • 111