0

I was writing a code to find the dimensions of a matrix to multiply them. However I am facing issue with the syntax.

#include <stdio.h>
#include <math.h>

double A[3][3] = {
    {1.23,0,0},
    {0,1,0},
    {0,0,1}
} ;

int B[3][3] = {
    {1,0,0},
    {0,1,0},
    {0,0,1}
} ;

void matrixdim(double mat[][4], int *i, int *j){
    *i = sizeof(mat) / sizeof(mat[0]);
    *j = sizeof(mat[0]) / sizeof(mat[0][0]);
}

int main(void){

    int k, r ;

    matrixdim(A, k, r) ;
    
    return 0 ;
}

Help is appreciated!

kartiks77
  • 48
  • 4
  • 5
    You're going to have a really hard time doing this with pointers, as pointers have *no idea* what the size of the original array they're pointing at is. Consider making a `struct` to define your matrix in a general way, as in having a dimension field, or have several, one for each common dimension. A lot of math libraries have, for example, `vec2`, `vec3`, `vec4`, etc. The thing you're missing here is [pointer decay](https://stackoverflow.com/questions/1461432/what-is-array-to-pointer-decay). Arrays in C are, at best, fancy pointers, and not especially fancy at that. – tadman Apr 26 '23 at 14:51
  • 2
    What do you think happens when you pass `double A[3][3]` to a function expecting a `double mat[][4]`? `3` is not the same as `4`. – Andrew Henle Apr 26 '23 at 14:52
  • Related (but no duplicate): [How to find the size of an array (from a pointer pointing to the first element array)?](https://stackoverflow.com/questions/492384/how-to-find-the-size-of-an-array-from-a-pointer-pointing-to-the-first-element-a). This applies for you because array parameters are passed as pointers. – Gerhardh Apr 26 '23 at 14:56
  • If at all, you might try with `double (*mat)[3][3]` – this is a pointer to a 2D array with both dimensions being 3. For correct code, you'd need to pass `&A` or `&B` to. This can be extended to VLA as `size_t n, double(*mat)[n][n]`, or with independent dimensions, if you want to multiply non-square matrices (you need three dimensions then!). But in any case: You need to pass the dimensions already from outside as function arguments. – Aconcagua Apr 26 '23 at 14:56
  • @tadman `vec2`, `vec3`, `vec4` – well, that gets pretty unhandy with matrices, doesn't it? If we don't have to cover just some very few special cases I'd to with a single `matrix` struct containing `size_t rows; size_t columns; double* data;` together with an `at` function doing the proper index calculation – and of course all those arithmetic functions... – Aconcagua Apr 26 '23 at 15:00
  • @Aconcagua It depends. If you're doing 3D work, you can get by with a handful of matrix types, you rarely need anything more sophisticated. If you're doing something more general, you need a more robust structure like what you've described. You can even use a "zero-sized" data structure and just allocate carefully with the correct amount of "extra" entries, where at least that way the memory for the matrix is contiguous. Beyond that and you're creating a general tensor library. – tadman Apr 26 '23 at 15:02
  • Thanks for the inputs. Is there any way of finding out the dimensions for any matrix without any prior knowledge? – kartiks77 Apr 26 '23 at 15:03
  • Side note: Assuming you *could* extract the dimensions then still this call: `int k, r; matrixdim(A, k, r);` won't work either; this would, if compiling at all (it's not correct code!) assign the uninitialised values of the ints (garbage) to the pointer. You need to call as `matrixDim(A, &k, &r)` to create pointers to the two ints (only `A`, as being an array, decays to pointer automatically). – Aconcagua Apr 26 '23 at 15:05
  • *'Is there any [...]'* – Well, you discovered the `sizeof(array)/sizeof(*array)` trick already. That's the only way if you do not have further information to the array – but you need to apply it **before** the array decays to a pointer, i.e. it is *not* possible from within a function you passed the array to. You could have a *macro* for, though. – Aconcagua Apr 26 '23 at 15:07
  • 1
    C++ would have some further possibilities (involving templates), but at the same time there raw arrays got – albeit still existing – superseded by `std::array`... – Aconcagua Apr 26 '23 at 15:10
  • Ahh understood. I shall try to make it work. Thanks @Aconcagua! – kartiks77 Apr 26 '23 at 15:13
  • 1
    @tadman Well – *'if we don't have to cover [...]'* – I've been with you in this respect already before... I like the flexible array member approach you mention ;) – Aconcagua Apr 26 '23 at 15:14
  • 1
    @kartiks77 With a macro: `#define ARRAY_SIZE ARRAY sizeof(ARRAY)/sizeof(*(ARRAY))` – and if you need sub-dimensions you could then go on with `#define SUB_DIM_SIZE ARRAY ARRAY_SIZE(*ARRAY)` – but still be aware that you need to apply these *before* the array decaying to pointer (e.g. by passing to a function), as mentioned already. – Aconcagua Apr 26 '23 at 15:19
  • I was just implementing that exact macro and it worked perfectly. Thanks! @Aconcagua – kartiks77 Apr 26 '23 at 15:25

1 Answers1

1

Short answer : you can't do that with simple arrays in C. A function that receives an array as a parameter has no way of knowing the dimensions.

The first solution I can think of to make this possible is to use a structure that will hold a pointer to the first value of the matrix and its dimensions.

#include <stdio.h>
#include <math.h>

typedef struct
{
    unsigned int size_x;
    unsigned int size_y;
    double *data;
} mat2D_t;


double A_data[3][3] = {
    {1.23,0,0},
    {0,1,0},
    {0,0,1}
};

double B_data[3][3] = {
    {1,0,0},
    {0,1,0},
    {0,0,1}
};

double C_data[3][3];

mat2D_t A = {
    .size_x = 3,
    .size_y = 3,
    .data = (double*)A_data,
};

mat2D_t B = {
    .size_x = 3,
    .size_y = 3,
    .data = (double*)B_data,
};

mat2D_t C = {
    .data = (double*)C_data,
};


int mat_mul(mat2D_t* a, mat2D_t* b, mat2D_t* res)
{
    unsigned int dim_ax = a->size_x;
    unsigned int dim_ay = a->size_y;
    unsigned int dim_bx = b->size_x;
    unsigned int dim_by = b->size_y;
    if (dim_by != dim_ax)
        return -1; // cannot multiply
    res->size_y = dim_ay;
    res->size_x = dim_bx;

    for (int y = 0; y < dim_ay; y++)
    {
        for (int x = 0; x < dim_bx; x++)
        {
            int index = y*dim_bx + x;
            res->data[index] = 0;
            for (int i = 0; i < dim_ax; i++)
            {
                res->data[index] += a->data[y*dim_ax + i]
                                  * b->data[i*dim_bx + x];
            }
        }
    }
    return 0;
}

int main(void){

    if (mat_mul(&A, &B, &C) != 0)
    {
        printf("dimensions error\n");
    }
    else
    {
        for (int y = 0; y < C.size_y; y++)
        {
            for (int x = 0; x < C.size_x; x++)
            {
                printf("%5.2f ", C.data[y*C.size_x + x]);
            }
            putchar('\n');
        }
    }
    
    return 0 ;
}

Note that you should also try to avoid global variables as much as possible.

You can also provide the dimensions of an array in the parameters of the function by putting the dimensions before the array :

void foo(int ax, int ay, double a[ay][ax])

But using this in a function that must return the result of the multiplication will be a bit difficult, and this may not be possible with th program you want to write.

Loïc France
  • 305
  • 2
  • 10