【问题标题】:Best way to allocate memory to a two-dimensional array in C?在C中为二维数组分配内存的最佳方法?
【发布时间】:2016-11-28 14:52:15
【问题描述】:

memory-managementspeed 这两个角度来看,将内存分配给C 中的two-d array 的最佳方法是什么?

另外,哪个更好用,two-d array(并为其分配内存)还是double pointer?谁能详细解释一下,里面发生了什么,为什么一种方法比另一种更好?

【问题讨论】:

  • 您有哪些关于所需尺寸的信息?它是固定的还是在程序运行时会改变?
  • 二维数组分配速度会更快,因为只有 1 个分配,也是连续的。但如果你要求太多连续,它可能会失败。

标签: c multidimensional-array malloc double-pointer


【解决方案1】:

为了获得最佳性能和最佳可读性,应始终将此类数组分配为连续的内存块:

type (*array) [X][Y] = malloc( sizeof(type[X][Y]) );

你应该避免这种情况:

// BAD METHOD, not a real array

type** lookup_table = malloc( X*sizeof(type*) );
for(size_t i=0; i<Y; i++)
{
  lookup_table[i] = malloc( Y*sizeof(type) );
}

前者速度更快,原因有很多。它被分配在一块连续的内存中,而不是在整个堆中分段。分段版本会阻止所有形式的代码优化和高效的片上数据缓存使用,而且实际分配也慢得多。

虽然上面的“坏”版本有一个优点,那就是当您希望各个维度具有可变长度时,例如在为字符串制作查找表时。然后你必须使用那个表格。但是如果你想要一个真正的二维数组,就没有理由不使用前者。


注意第一个版本通常写成

type (*array) [Y] = malloc( sizeof(type[X][Y]) );

为了更方便的使用:array[i][j],而不是可读性较差的(*array)[i][j]

【讨论】:

  • 很好的解释和我的一个,但是为什么(*array)[Y]可以让你通过使用array[i][j]访问/分配它但是当你使用(*array)[X][Y]创建它时你必须使用(*array)[i][j]为以后的操作?
  • @Yahya 和 int (*array)[Y]array 是一个指向大小为 Y 的数组的指针。这意味着它有两个级别可以取消引用 - 所以 array[row] 给出一个数组,array[row][col] 给出你是int。使用int (*array)[X][Y],现在有 3 个级别——它是一个指向二维整数数组的指针。所以你需要第一个(*array) 星号来允许双索引访问。
【解决方案2】:
data_type (*mat)[size_2] = malloc(size_1 * size_2 * sizeof(data_type));

这将为数组数组(“二维数组”)分配连续的内存。如果您不需要荒谬的1 数量的空间,那么这就是要走的路。使用malloc,您将减少内存碎片、提高缓存友好性并避免过多开销。


1对于一些(特定于应用程序的)荒谬的定义

【讨论】:

  • data_type (*mat)[size_2] = malloc( sizeof *mat * size_1 ); 更好。 sizeof *mat 等价于 sizeof (data_type[size_2])
  • @JohnBode,谢谢。我在(错误的)印象中sizeof 不会对 VLA 表现良好
  • 关于 VLA,情况并非 100% 清楚。是的,如果您从字面上阅读标准,如果 mat 是 VLA,我的版本应该调用 UB。然而,我们中的一些人认为该标准在这方面的措辞很糟糕。您没有理由必须取消引用指针 来获取 VLA 的尺寸。实现必须携带某种元数据才能使 VLA 工作 - 没有理由相信它在评估 sizeof 时不能只使用该元数据。
【解决方案3】:

给定一个固定大小,您可以简单地说twoDimArray[100][100],它将在堆栈上分配它。但是,在堆上分配时(无论是因为大小非常大还是因为大小是动态的),您有更多选择。

您可以分配一个指针数组,然后循环为每一行分配内存。这对于缓存局部性来说是有问题的,但是如果大小非常大并且您的访问是顺序的,则非常好;它允许合理数量的碎片,而不会对性能产生巨大影响,因为数组的数组可以与数组本身分开,而数组本身可以彼此分开。在线性访问场景中,您大部分不会在内存区域之间跳转;相反,您甚至可以在移动到新区域之前访问整条线路。

第二种方式是线性化访问,一次性全部分配;即为sizex * sizey分配足够的内存,然后用(positiony * sizex) + positionx索引它;也就是说,倒数一些行,然后跨一些列。这非常适合随机访问并改善缓存局部性,因为内存是连续的,但如果没有足够的连续内存可用,它可能会失败(如果您需要的内存多于缓存,则缓存局部性的好处不适用)。

【讨论】:

    猜你喜欢
    • 2018-05-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-05
    • 1970-01-01
    • 1970-01-01
    • 2015-02-01
    相关资源
    最近更新 更多