【问题标题】:How do I allocate memory for a multidimensional array?如何为多维数组分配内存?
【发布时间】:2021-11-15 10:43:48
【问题描述】:

如何使用malloc为多维数组分配内存?

例如,您想使用arr[6][9]

您可能已经尝试过以下方法:

// Warning: broken example
int **arr = malloc(50 * sizeof(int));

arr[6][9] = 42; // dangerous! Segmentation fault (core dumped)

这显然是错误的。但是为多维数组分配(和释放)内存的正确方法是什么?

【问题讨论】:

    标签: c multidimensional-array malloc


    【解决方案1】:

    最基本的多维数组当然是二维数组。 它有两个维度,在本例中,我将使用大小为x 的数组y。 为简单起见,我使用整数类型来存储数据。存储类型与使用的一般技术无关。

    为清楚起见,在前几个示例中跳过了任何错误检查。 后面的示例包括一些基本形式的错误检查。 size_t 类型用于索引偏移,以避免与存储在多维数组中的类型(整数)混淆。

    基本 2D 示例

    /*
     * Warning: no error checks!
     */ 
    int **create_2d(size_t x, size_t y)
    {
        int *values = malloc(x * y * sizeof *values);
        int **index_x = malloc(x * sizeof *index_x);
    
        for (size_t i = 0; i < x; i++)
            index_x[i] = &values[i * y];
    
        return index_x;
    }
    

    您现在可以读取和写入二维数组中的所有位置,只要您不低于0 或超过xy,因为那样会越界访问数组.

    int **arr = create_2d[20][24];
    
    arr[6][9] = 42; // perfectly fine!
    

    也许您对此代码感到满意,并将其复制/粘贴到您的项目中。 这完全没问题,但风险自负。我将提供进一步的解释和一些警告。


    对这一切意味着什么的一些解释。最后,多维数组需要存储x 行和y 类型int 的列。这意味着所需的存储大小至少为x * y * sizeof(int)。 在这个例子中,所有需要的存储都是一次性分配的。但是,使用 sizeof *values 而不是 sizeof(int),因为这更容易维护,例如应该存储类型更改。这种方式更不容易出错。

    现在,所有内存都是“连续的”,可以作为从values[0]values[x * y] 的偏移量访问。通过使用一些简单的算术,这实际上通常已经可以用作人造二维数组。例如,您可以说索引(i,j) 已经可以通过values[i * y + j]; 访问。第一个y 值是行0,接下来的y 值是行1,等等。

    要通过索引[i][j] 真正访问该索引,还需要实际分配该索引。在这种情况下,我称之为index_x。它必须能够指向 x 不同的内存位置,特别是每个“行”的“第一个”y 值。

    您经常会看到人们循环执行分配。这实际上是没有必要的,并且在错误检查和释放方面使事情变得更加复杂。尽管如此,为y-rows 的开头分配内存位置需要在循环中完成,其中我使用i 作为迭代器值,范围从0x。因为index_x需要指向指针,所以我们把values[i * y]地址放在index_x里面。

    需要注意的是,返回的也是index_x,而不是values。如果您实际上需要访问values,仍然可以通过index_x[0] 完成。当我们需要释放内存时,这会很方便。

    基本释放 2D 示例

    以下函数将free向上分配的内存:

    /*
     * Warning: no error checks!
     */
    void destroy_2d(int **ptr)
    {
        free(ptr[0]);
        free(ptr);
    }
    

    如您所见,这里不需要循环。

    现在可能不清楚为什么 with 比在循环中使用 malloc 更可取。一旦你开始添加错误检查代码,或者当你需要分配很多项目或有很多嵌套时,它就会变得很明显。相同的原理适用于 3 维数组。为了清楚起见,让我演示一下 3D 数组:

    基本 3D 示例

    int ***create_3d(size_t x, size_t y, size_t z)
    {
        int *values = malloc(x * y * z * sizeof *values);
        int **index_y = malloc(x * y * sizeof *index_y);
        int ***index_x = malloc(x * sizeof *index_x);
    
        for (size_t i = 0; i < x; i++) {
            index_x[i] = &index_y[i * y];
            for (size_t j = 0; j < y; j++) {
                // remove ONE of the following two lines
                index_x[i][j] = &values[(i * y + j) * z]; // or, alternatively:
                index_y[i * y + j] = &values[(i * y + j) * z]; // this is exactly the same
            }
        }
    
        return index_x;
    }
    
    void destroy_3d(int ***ptr)
    {
        free(ptr[0][0]);
        free(ptr[0]);
        free(ptr);
    }
    

    这是相同的原理,虽然算术更复杂。


    让我通过添加非常基本的错误检查来告诉你为什么这很重要:

    带错误检查的基本 3D 示例

    int ***create_3d_e(size_t x, size_t y, size_t z)
    {
        int *values = malloc(x * y * z * sizeof *values);
        if (!values)
            return NULL;
    
        int **index_y = malloc(x * y * sizeof *index_y);
        if (!index_y) {
            free(values);
            return NULL;
        }
    
        int ***index_x = malloc(x * sizeof *index_x);
        if (!index_x) {
            free(index_y);
            free(values);
            return NULL;
        }
    
        for (size_t i = 0; i < x; i++) {
            index_x[i] = &index_y[i * y];
            for (size_t j = 0; j < y; j++) {
                index_y[i * y + j] = &values[(i * y + j) * z];
            }
        }
    
        return index_x;
    }
    

    或者,如果您更喜欢不同的代码风格:

    int ***create_3d_g(size_t x, size_t y, size_t z)
    {
        int *values;
        int **index_y;
        int ***index_x;
        size_t i, j;
    
        values = malloc(x * y * z * sizeof *values);
        if (!values)
            goto err;
    
        index_y = malloc(x * y * sizeof *index_y);
        if (!index_y)
            goto err_y;
    
        index_x = malloc(x * sizeof *index_x);
        if (!index_x)
            goto err_x;
    
        for (i = 0; i < x; i++) {
            index_x[i] = &index_y[i * y];
            for (j = 0; j < y; j++) {
                index_y[i * y + j] = &values[(i * y + j) * z];
            }
        }
    
        return index_x;
    
    err_x:
        free(index);
    err_y:
        free(values);
    err:
        return NULL;
    }
    

    然后是一些基本的释放时防止错误的逻辑:

    带有错误检查的基本释放 3D 示例

    void destroy_3d_e(int ***ptr)
    {
        if (ptr) {
            if (ptr[0]) {
                free(ptr[0][0]);
                free(ptr[0]);
            }
            free(ptr);
        }
    }
    

    这是不在循环内分配内存的另一个优点!在这种情况下,“销毁”函数还应该知道维度和free 循环中的所有分配。当嵌套多维数组的循环中某些分配中途失败时,增加了复杂性。崩溃您的程序并不总是一种选择,您可能希望或需要释放内存以防止令人讨厌的错误。那时释放“连续”内存比“循环-malloc”方法容易得多。我没有为此提供示例,因为我认为这不会有帮助。如果其他人想将其作为单独的答案提供,请提供相应的保留意见。


    作为读者练习:尝试为 3 维数组实现它。在构建数组的中途检查失败,并优雅地拆除所有内容而不会出现内存泄漏。

    HEAP SUMMARY:
        in use at exit: 0 bytes in 0 blocks
        total heap usage: 3 allocs, 3 frees, 96,481,600 bytes allocated
    
    All heap blocks were freed -- no leaks are possible
    
    ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
    

    我希望将来要求这种方法的人要少得多。我希望这些示例能让您更好地了解多维数组的内部工作原理。

    【讨论】:

    • Pointer-to-pointers 对于实现多维数组效率低下,不应该作为一种常规技术来教授。 (它们使处理器无法进行内存查找,干扰流水线等。)C 标准定义了可变长度数组,并且许多编译器都支持它们,因此只需使用int (*p)[y] = malloc(x * sizeof *p); 就可以一步分配多维数组。
    • 失败了,将所谓的“人造”数组分配为具有手动地址计算的一维数组比指针指向指针更可取。许多处理器具有对某些地址计算的内置支持的指令,并且地址计算通常可以由编译器部分优化和/或由处理器比指针查找更快地计算。所以程序员可能需要更多的代码(可以通过内联函数或宏来简化),但这是值得的。
    • Correctly allocating multi-dimensional arrays。这就解释了指向指针类型数组的指针和真正的多维数组。
    猜你喜欢
    • 2021-07-21
    • 2015-02-01
    • 1970-01-01
    • 2012-10-07
    • 2012-11-30
    • 1970-01-01
    • 2015-06-08
    • 2012-09-04
    相关资源
    最近更新 更多