C:在另一个函数中初始化动态类型的二维矩阵

C: Initializing a dynamically typed 2D matrix inside another function

我正在尝试创建 3 个动态类型的矩阵(int、float、double)以用于矩阵乘法。我已经为主矩阵中的每个矩阵创建了一个 void** "typed" 容器,然后将它们(作为 void***)传递给一个 init 函数,以便根据用户对类型的选择进行 malloc。下面的代码可以编译,但是在 j 循环的一次迭代后我遇到了分段错误,我终究无法弄清楚为什么会发生这种情况。同样,如果我要在一个单独的 2 层深度循环中进行初始化(从 malloc 循环中取出 j-loop),那么在 i-loop 的一次迭代之后仍然会发生分段错误。

这甚至是实现我的动态类型矩阵乘法目标的好方法吗?非常感谢您的帮助。

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);

}

实际上,我一直在独自完成与此完全相同的事情。我对如何做到这一点的建议是实际上放弃你正在做的方式。从它的外观来看,你有一个指针数组(可以说是一个 int 指针数组),并且每个指针都有一个自己的数组,基本上是一个这样声明的变量 int **exampleMatrix。你这样做实际上有一个很大的问题,那就是缓存未命中。更好的方法如下

#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;
}

你当然必须决定你的矩阵是行主还是列主。我希望这是有道理的,英语不是我的第一语言,有时我不是最擅长解释的。如果您需要我更好地解释它,请告诉我:)

你分配内存的方式很奇怪。

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

matA 指向哪里??,除非您分配了一些内存并使 matA 指向此函数之外的它,否则您正在消耗内存。

Malloc returns 指向已分配内存的指针,它不会在您的随机指针指向的任何地方分配它。

你也不需要三层间接寻址***matA,两层就够了。

这是您的代码,其中包含我建议的更改 - disclaimer:I 实际上并没有尝试过,但我认为它会起作用 -:

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" );
    }
}

调用时:

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

要使用动态分配的二维数组,应使用正确的指针类型。 (int **) 是指向指针的指针,指向指针数组的第一个元素,这些指针本身指向不同的分配。这种代码的结果是一个锯齿状的数组,而不是一个二维数组。分配的内存不保证是连续的(因为数组分配必须是连续的):

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);
}

一种可能性是简单地为一维数组分配存储空间,并根据二维数组索引计算该数组的偏移量。这工作正常,但结果不是二维数组:

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

这不能作为二维数组索引,但可以根据列数和二维数组索引计算出一维索引:

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;
    }
}

这两种方法都有其用途,但它们都有一个缺点,即生成的数组无法传递给旨在使用二维数组的函数。也就是说,考虑一个打印二维数组的函数,例如:

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');
    }
}

此函数适用于:

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

但它不适用于更早的 jagged_arr:

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

simulated_2d_arr:

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

在上面的错误消息中可以看到动态分配二维数组时要使用的正确类型。对于 int 的二维数组,这将是 int (*)[]。这是二维数组在大多数表达式(包括函数调用)中衰减的类型。因此,要动态分配 int 的二维数组,这将起作用:

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

这为 num_rowsnum_cols int 数组分配了 space。请注意,这不会创建 VLA,但会使用 VLA type。当然,VLA 在 C99 中被引入,但在 C11 中成为可选的(尽管仍然得到广泛支持)。

至于你问题的动态类型部分,一个选择是创建一个 enum 来保存类型标识符,并将这些枚举常量之一传递给任何需要它们的函数。这些函数将需要接受 (void *) 个参数,这些参数将根据类型枚举常量进行适当转换。这有点复杂,但这是一个示例程序。请注意,print_array() 函数适用于动态分配的数组和静态大小的数组。另请注意,不需要三重甚至双重间接寻址!

#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');
    }
}

程序输出:

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

您的基本问题是后缀运算符的优先级高于前缀运算符。所以当你说

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

你正在获得

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

当你想要的是

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

所以你需要明确的括号才能让它工作。同样,代替

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

你需要

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