Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C matrix struct

I am learning C and having a hard time identifying what I am doing wrong since I am getting a segmentation fault. I am trying to initialize a Matrix structure which contains a pointer to a 2D array with the actual data. Then fill it with data from an array and print it.

#include "base.h" 

struct Matrix {
    int rows; // number of rows
    int cols; // number of columns
    double** data; // a pointer to an array of n_rows pointers to rows
};
typedef struct Matrix Matrix;

Matrix* make_matrix(int n_rows, int n_cols) {
    struct Matrix matrix;
    matrix.rows = n_rows;
    matrix.cols = n_cols;
    matrix.data = (double**)malloc(sizeof(double*) * n_rows);
    for(int x = 0; x < n_rows; x++){
        matrix.data[x] = (double*)calloc(n_cols, sizeof(double));
    }
    struct Matrix *m;
    m = &matrix;
    return m;
}

Matrix* copy_matrix(double* data, int n_rows, int n_cols) {
    struct Matrix *matrix = make_matrix(n_rows, n_cols);
    for(int x = 0; x < n_rows; x++) {
        for(int y = 0; y < n_cols; y++) {
            matrix->data[x][y] = data[x+y];
        }
    }
    return matrix;
}

void print_matrix(Matrix* m) {
    for(int x = 0; x < m->rows; x++) {
        for(int y = 0; y < m->cols; y++) {
            printf("%f", m->data[x][y]);
        }
    }
}

void matrix_test(void) {

    double a[] = { 
        1, 2, 3, 
        4, 5, 6, 
        7, 8, 9 };
    Matrix* m1 = copy_matrix(a, 3, 3);
    print_matrix(m1);
}

int main(void) {
    base_init();
    base_set_memory_check(true);
    matrix_test();
    return 0;
}

Also, what is there to change to the better besides the segmentation fault triggering error?

like image 399
John Alba Avatar asked Jan 04 '23 22:01

John Alba


2 Answers

Welcome to C. Two big issues here:

1) You can't return pointer to local variable of function. (relates to bug in make_matrix())

2) There is no obvious way to define 'multidimensional' array in C where you can conveniently access an element like data[x][y], unless your row size is known and fixed at compile time. (And your matrix dimension is not known at compile time.)

Let's deal with them separately.

To solve 1), what you want to do in make_matrix() is actually:

Matrix* make_matrix(int n_rows, int n_cols) {
    struct Matrix* pmatrix = malloc(sizeof(struct Matrix));
    pmatrix->rows = n_rows;
    pmatrix->cols = n_cols;
    ...
    ...
    return pmatrix;
}

But this alone can't fix the bug. The data need to be stored in one big array of cols*rows elements and you need to specify how to locate each item instead of data[x][y].

So to solve 2), your Matrix struct definition should be

struct Matrix {
    int rows; // number of rows
    int cols; // number of columns
    double* data; // <- note that this is a pointer to one dim array
};

, and inside make_matrix()

pmatrix->data = malloc(sizeof(double)*n_rows*n_cols);

is all you need now.

To duplicate a matrix of same dimension and format,

Matrix* copy_matrix(double* data, int n_rows, int n_cols) {
    struct Matrix *matrix = make_matrix(n_rows, n_cols);
    for(int i = 0; i < n_rows*n_cols; i++)
        matrix->data[i] = data[i];
    return matrix;
}

(I would name this function as dup_matrix() instead of copy_matrix() because it actually creates a new instance)

And finally if you want to access an element at [x][y], the position should be explicitly calculated as data[x*cols + y], so the printf() line becomes

printf("%f ", m->data[x*(m->cols) + y]);

Of course you need to properly check return from malloc() for error and properly clean up.

like image 63
Yogi Jason Avatar answered Jan 09 '23 08:01

Yogi Jason


Thank you for your responses, I have learned where I failed. For the sake of completeness, the working code looks like this:

#include "base.h"

struct Matrix {
    int rows; // number of rows
    int cols; // number of columns
    double** data; // a pointer to an array of n_rows pointers to rows; a row is an array of n_cols doubles 
};
typedef struct Matrix Matrix;

Matrix* make_matrix(int n_rows, int n_cols) {
    struct Matrix* matrix = malloc(sizeof(Matrix));
    matrix->rows = n_rows;
    matrix->cols = n_cols;
    double** data = malloc(sizeof(double*) * n_rows); 
    for(int x = 0; x < n_rows; x++){
        data[x] = calloc(n_cols, sizeof(double));
    }
    matrix->data = data;
    return matrix;
}

Matrix* copy_matrix(double* data, int n_rows, int n_cols) {
    struct Matrix *matrix = make_matrix(n_rows, n_cols);
    for(int x = 0; x < n_rows; x++) {
        for(int y = 0; y < n_cols; y++) {
            matrix->data[x][y] = data[n_cols*x+y];
        }
    }
    return matrix;    
}

void print_matrix(Matrix* m) {
    for(int x = 0; x < m->rows; x++) {
        printf("%s", "\n");
        for(int y = 0; y < m->cols; y++) {
            printf("%f\t", m->data[x][y]);
        }
    }
}

void matrix_test(void) {   

    double a[] = { 
        1, 2, 3, 
        4, 5, 6, 
        7, 8, 9,
        10,11,12
        };
    Matrix* m1 = copy_matrix(a, 4, 3);
    print_matrix(m1);
}

int main(void) {
    base_init();
    base_set_memory_check(true);
    matrix_test();
    return 0;
}

If there are more improvements to make, or hurt conventions coming to your minds, please tell me.

like image 22
John Alba Avatar answered Jan 09 '23 06:01

John Alba