2
votes

I am trying to create 3 matrices which are dynamically typed (int, float, double) for the purpose of matrix multiplication. I've created a void** "typed" container for each matrix in the main, then passing them (as void***) into an init function to be malloc'd based on user selections for the type. The code below compiles, but I am getting a segmentation fault after one iteration of the j loop, and I can't figure out for the life of me why this happens. Similarly if I were to do the initialization in a separate 2-deep loop (take out j-loop from malloc loop), then the segmentation error occurs after one iteration of the i-loop still.

Is this even a good way to accomplish my goal of dynamic-typed matrix multiplication? Thank you very much for the help.

void initMat(int type, int matSize, void ***matA, void ***matB, void ***matC)
{
    int i, j, k;

    switch(type) {
        case 0 :
        *matA = malloc(matSize * sizeof(int*));
        *matB = malloc(matSize * sizeof(int*));
        *matC = malloc(matSize * sizeof(int*));
        for (i = 0; i < matSize; i++) {
            *matA[i] = malloc(matSize * sizeof(int));
            *matB[i] = malloc(matSize * sizeof(int));
            *matC[i] = malloc(matSize * sizeof(int));
            for (j = 0; j < matSize; j++) {
                *(int*)matA[i][j] = rand()/RAND_MAX * 10;
                *(int*)matB[i][j] = rand()/RAND_MAX * 10;
                *(int*)matC[i][j] = 0;
            }
        }
        break;

        case 1 :
        // with float, double, etc.
        break;

        default :
        printf("Invalid case.\n" );
    }
}

int main()
{
    int type = 0;
    int size = 0;
    void **matA, **matB, **matC;

    int sizes[6] = {3, 4, 5};
    int matSize = sizes[size];
    printf("The selected matrix size is: %d. \n", matSize); //allows user to select matrix size

    initMat(type, matSize, &matA, &matB, &matC);

//  displayMat(matSize, matA);

}
4
why are you using *(int*) ? - hassan arafat
Use GDB and check where your program is not giving the desired output or it is getting crashed - Vivek Singh
like this - BLUEPIXY
@hassanarafat it's to cast the void pointer into a int point before dereferencing it - M.Lee
@VivekSingh I used checkpoints to narrow down the problem to which exact iteration the seg fault occurs. And the thing is that the implementation is exactly like answers from other questions of similar style (except with the matrix passing and dynamic typing - so I can only assume it's one of those reasons messing it up) - M.Lee

4 Answers

1
votes

To work with dynamically allocated 2d arrays, correct pointer types should be used. (int **) is a pointer to a pointer, pointing to the first element of an array of pointers which themselves point to disparate allocations. The result of this sort of code is a jagged array, but not a 2d array. The allocated memory is not guaranteed to be contiguous (as array allocations must be):

size_t num_rows = 3;
size_t num_cols = 5;

int **jagged_arr = malloc(sizeof *jagged_arr * num_rows);

for (size_t i = 0; i < num_rows; i++) {
    jagged_arr[i] = malloc(sizeof *jagged_arr[i] * num_cols);
}

One possibility is to simply allocate storage for a 1d array, and calculate offsets into this array from 2d array indices. This works fine, but the result is not a 2d array:

size_t num_elems = num_rows * num_cols;
int *simulated_2d_arr = malloc(sizeof *simulated_2d_arr * num_elems);

This can't be indexed as a 2d array, but the 1d index can be calculated from the number of columns and the 2d array indices:

for (size_t i = 0; i < num_rows; i++) {
    for (size_t j = 0; j < num_cols; j++) {
        simulated_2d_arr[i * num_cols + j] = i * num_cols + j;
    }
}

Both of these approaches have their uses, but they suffer from a disadvantage in that the resulting arrays can't be passed to functions which are meant to work with 2d arrays. That is, consider a function to print a 2d array, such as:

void print_2d_arr(size_t rows, size_t cols, int arr[][cols])
{
    for (size_t i = 0; i < rows; i++) {
        for (size_t j = 0; j < cols; j++) {
            printf("%5d", arr[i][j]);
        }
        putchar('\n');
    }
}

This function will work for something like:

int real_2d_arr[2][3] = { { 1, 2, 3 },
                          { 4, 5, 6 } };

But it will not work for the earlier jagged_arr:

expected ‘int (*)[(sizetype)(cols)]’ but argument is of type ‘int **’

or for simulated_2d_arr:

expected ‘int (*)[(sizetype)(cols)]’ but argument is of type ‘int *’

The correct type to use when dynamically allocating 2d arrays is seen in the above error messages. For a 2d array of ints, that would be int (*)[]. This is the type that a 2d array decays to in most expressions, including function calls. So, to dynamically allocate a 2d array of ints, this would work:

size_t num_rows = 3;
size_t num_cols = 5;
int (*array_2d)[num_cols] = malloc(sizeof *array_2d * num_rows);

This allocates space for num_rows arrays of num_cols ints. Note that this does not create a VLA, but the VLA type is used. Of course, VLAs were introduced back in C99, but were made optional in C11 (though still widely supported).

As for the dynamic type part of your question, one option would be to create an enum to hold type identifiers, and pass one of these enumeration constants to whatever functions need them. These functions will need to accept (void *) arguments, which will be appropriately converted based on the type enumeration constant. This is a little more involved, but here is an example program. Note that the print_array() function works for both the dynamically allocated arrays, and for a statically sized array. Also note that there is no need for triple, or even double indirection!

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

enum Type { CHAR,
            INT,
            FLOAT,
            DOUBLE };

void * get_array(enum Type type, size_t rows, size_t cols);
void init_array(enum Type type, size_t rows, size_t cols, void *arr);
void print_array(enum Type type, size_t rows, size_t cols, void *arr);

int main(void)
{
    char (*arr_char)[5] = get_array(CHAR, 4, 5);
    int (*arr_int)[5] = get_array(INT, 4, 5);
    double (*arr_double)[5] = get_array(DOUBLE, 4, 5);

    int arr_static[][3] = { { 1, 2, 3 },
                            { 4, 5, 6 },
                            { 7, 8, 9 } };

    if (arr_char) {                           // check for null pointer
        init_array(CHAR, 4, 5, arr_char);
        puts("4x5 array of char");
        print_array(CHAR, 4, 5, arr_char);
        putchar('\n');
    }

    if (arr_int) {                            // check for null pointer
        init_array(INT, 4, 5, arr_int);
        puts("4x5 array of int");
        print_array(INT, 4, 5, arr_int);
        putchar('\n');
    }

    if (arr_double) {                         // check for null pointer
        init_array(DOUBLE, 4, 5, arr_double);
        puts("4x5 array of double");
        print_array(DOUBLE, 4, 5, arr_double);
        putchar('\n');
    }

    puts("Statically sized 3x3 array of int");
    print_array(INT, 3, 3, arr_static);
    putchar('\n');

    /* Cleanup */
    free(arr_char);
    free(arr_int);
    free(arr_double);

    return 0;
}

/* Returns null pointer on allocation failure */
void *get_array(enum Type type, size_t rows, size_t cols)
{
    size_t array_sz = 0;
    void *ret = NULL;

    switch (type) {
    case CHAR:
        array_sz = sizeof (char) * rows * cols;
        break;
    case INT:
        array_sz = sizeof (int) * rows * cols;
        break;
    case FLOAT:
        array_sz = sizeof (float) * rows * cols;
        break;
    case DOUBLE:
        array_sz = sizeof (double) * rows * cols;
        break;
    default:
        fprintf(stderr, "Unrecognized type in get_array()");
    }

    if (array_sz) {
        ret = malloc(array_sz);
    }

    return ret;
}

void init_array(enum Type type, size_t rows, size_t cols, void *arr)
{
    for (size_t i = 0; i < rows; i++) {
        for (size_t j = 0; j < cols; j++) {
            int offset = i * cols + j;
            switch (type) {
            case CHAR:
            {
                char (*array_char)[cols] = arr;
                array_char[i][j] = 'a' + offset;
                break;
            }
            case INT:
            {
                int (*array_int)[cols] = arr;
                array_int[i][j] = 0 + offset;
                break;
            }
            case FLOAT:
            {
                float (*array_float)[cols] = arr;
                array_float[i][j] = 0.0 + offset;
                break;
            }
            case DOUBLE:
            {
                double (*array_double)[cols] = arr;
                array_double[i][j] = 0.0 + offset;
                break;
            }
            default:
                fprintf(stderr, "Unrecognized type in get_array()");
            }
        }
    }
}

void print_array(enum Type type, size_t rows, size_t cols, void *arr)
{
    for (size_t i = 0; i < rows; i++) {
        for (size_t j = 0; j < cols; j++) {
                switch (type) {
            case CHAR:
            {
                char (*array_char)[cols] = arr;
                printf("%3c", array_char[i][j]);
                break;
            }
            case INT:
            {
                int (*array_int)[cols] = arr;
                printf("%5d", array_int[i][j]);
                break;
            }
            case FLOAT:
            {
                float (*array_float)[cols] = arr;
                printf("%8.2f", array_float[i][j]);
                break;
            }
            case DOUBLE:
            {
                double (*array_double)[cols] = arr;
                printf("%8.2f", array_double[i][j]);
                break;
            }
            default:
                fprintf(stderr, "Unrecognized type in get_array()");
            }
        }
        putchar('\n');
    }
}

Program output:

4x5 array of char
  a  b  c  d  e
  f  g  h  i  j
  k  l  m  n  o
  p  q  r  s  t

4x5 array of int
    0    1    2    3    4
    5    6    7    8    9
   10   11   12   13   14
   15   16   17   18   19

4x5 array of double
    0.00    1.00    2.00    3.00    4.00
    5.00    6.00    7.00    8.00    9.00
   10.00   11.00   12.00   13.00   14.00
   15.00   16.00   17.00   18.00   19.00

Statically sized 3x3 array of int
    1    2    3
    4    5    6
    7    8    9
1
votes

Your basic problem is that postfix operators are higher precedence than prefix operators. So when you say

*matA[i] = malloc(matSize * sizeof(int));

you are getting

*(matA[i]) = malloc(matSize * sizeof(int));

when what you want is

(*matA)[i] = malloc(matSize * sizeof(int));

so you need the explicit parenthesis to make it work. Similarly, instead of

*(int*)matA[i][j] = rand()/RAND_MAX * 10;

you need

((int**)*matA)[i][j] = rand()/RAND_MAX * 10;
1
votes

I've actually been working on something exactly like this on my own. My advice on how to do this is to actually abandon the way you're doing it. From the looks of it you have an array of pointers (lets say an array of int pointers), and each one of those pointers has an array of its own, basically a variable declared as such int **exampleMatrix. There's actually a huge issue with you doing it this way which is cache misses. A better way for you to do this is as follows

#include <stdlib.h>

void *initMat(size_t rows, size_t cols, int type);

int main(void){
    int *matrix = initMat();
    free(matrix);
    return 0;
}

void *initMat(size_t rows, size_t cols, int type){
    //check for what type to allocate here
    void *matrix = malloc(sizeof(int)*rows*cols);
    //check for errors here
    return matrix;
}

you of course will have to decide if your matrix is row or column major. I hope this makes sense, English isn't my first language and I'm not the best at explaining sometimes. If you need me to explain it better just tell me :)

-1
votes

The way you're allocating memory is weird.

*matA = malloc(matSize * sizeof(int*));

Where does matA point??, unless you allocated some memory and made matA point to it outside this function then you are thrashing you memory.

Malloc returns a pointer to allocated memory, it doesn't allocate it wherever your random pointer is pointing.

You also don't need three levels of indirection***matA, two is enough.

This is your code with the changes I suggested - disclaimer:I didn't actually try this but I think it'll work-:

void initMat(int type, int matSize, void ***matA, void ***matB, void ***matC)
{
    int i, j, k;

    switch(type) {
        case 0 :
            *matA = malloc(matSize * sizeof(int*));
            *matB = malloc(matSize * sizeof(int*));
            *matC = malloc(matSize * sizeof(int*));
            for (i = 0; i < matSize; i++) {
                (*matA)[i] = malloc(matSize * sizeof(int));
                (*matB)[i] = malloc(matSize * sizeof(int));
                (*matC)[i] = malloc(matSize * sizeof(int));
                for (j = 0; j < matSize; j++) {
                    (*matA)[i][j] = rand()/RAND_MAX * 10;
                    (*matB)[i][j] = rand()/RAND_MAX * 10;
                    (*matC)[i][j] = 0;
                }
            }
        break;

        case 1 :
        // with float, double, etc.
        break;

        default :
            printf("Invalid case.\n" );
    }
}

and when calling:

int type,matSize;
//whatever type you like
Type **matA,**matB,**matC; 
//your code here
initMat(type,matSize,&matA,&matB,&matC);
//the rest of your code here