【问题标题】:Difference between two methods of mallocmalloc的两种方法的区别
【发布时间】:2016-02-02 21:49:25
【问题描述】:

我想创建 5*5 2D 矩阵。我通常使用以下内存分配方式:

int **M  = malloc(5 * sizeof(int *));
for (i = 0; i < 5; i++)
{
    M[i] =  malloc(5 * sizeof(int));
}

在阅读博客时,我还发现了另一种方法:

int **M = malloc(5 * sizeof(int*));
M[0] = malloc((5*5) * sizeof(int));

我的问题是:这两种方法有什么区别?哪个效率更高?

【问题讨论】:

  • 第二个 sn-p 不完整,因为它使 M[1]M[4] 未初始化。
  • 第一个更“高效”,因为它会做你真正想要的(为每个M[i] 分配5 个int 值的空间)。第二个为M[0] 的25 个int 值分配空间,使M[1]M[4] 未初始化。您在哪个博客中找到了这个?
  • 区别在于第一个有效,第二个是bug。

标签: c


【解决方案1】:

对于第二个代码,请注意您需要初始化其他数组成员才能使其正常工作:

    for (int i = 1; i < 5; i++) {
        M[i] = M[0] + i * 5;
    }

所以在第二个代码中,数组成员(通过所有数组)是连续的。访问它们没有任何区别(例如,您仍然可以使用 M[i][j] 语法访问它们)。与第一个代码相比,它的优势在于只需要两次 malloc 调用,并且如 cmets 中所述,它支持缓存,可以大大提高访问性能。

但如果你打算动态分配大数组,最好使用第一种方法,因为内存碎片(大的连续内存分配可能不可用或者会加剧内存碎片)。

这种动态分配数组的类似例子可以在c-faq中找到:http://c-faq.com/aryptr/dynmuldimary.html

【讨论】:

  • 第二种情况,如果你尝试访问M[1][1]会发生什么?
  • -1;第二种情况没有做正确的事情。此外,如果它确实做了“正确”的事情,也就是分配M[1] = &amp;M[0][5] 等,那么它肯定确实由于缓存而在性能方面有所不同。
  • @ouah:呸。我的意思是,是的,它可以工作,但是......糟糕。假设 OP 遵循该技术的后半部分(分配 M[1]M[4]
  • @JohnBode:我不明白想要连续内存有什么“讨厌”...
  • @R_Kapp:T (*arr)[N] = malloc( sizeof *arr * M );。连续,无需手动分配额外的指针。
【解决方案2】:

在看到 ouah 的回答和 C 常见问题解答中的示例之后,我现在明白了第二种技术的来源,尽管我个人不会在可以帮助的地方使用它。

您展示的第一种方法的主要问题是数组中的行不能保证在内存中是相邻的; IOW,紧跟在M[0][4] 之后的对象不一定是M[1][0]。如果从不同页面分配两行,则可能会降低运行时性能。

第二种方法保证所有行将被连续分配,但您必须手动分配M[1]M[4] 以使正常的M[i][j] 下标工作,如

for ( size_t i = 0; i < 5; i++ )
  M[i] = M[i-1] + 5;

IMO 与以下方法相比,这是一种笨拙的方法:

int (*M)[5] = malloc( sizeof *M * 5 );

这也保证了内存是连续分配的,M[i][j] 下标无需任何额外努力即可工作。

但是,有一个缺点;在不支持可变长度数组的编译器上,数组大小必须在编译时知道。除非你的编译器支持 VLA,否则你不能这样做

size_t cols;
...
int (*M)[cols] = malloc( sizeof *M * rows );

在这种情况下,M[0] = malloc( rows * cols * sizeof *M[0]) 之后手动分配 M[1]M[rows - 1] 将是一个合理的替代品。

【讨论】:

  • 我希望我在发帖前看到这个。这是正确的答案。
  • @ouah:不,它是一个指向 VLA 的 指针。数组本身将从堆中分配。
  • @ouah:是的,这是该技术的一个缺点;如果您没有 VLA 支持,则必须在编译时指定外部尺寸的大小。或者你使用不同的技术。
  • @JohnBode 实际上,我确实认为在 C99(以及之后的 C 版本)上,这是动态分配连续数组数组的最佳解决方案。
  • 好消息是除了 MSVC 之外的所有编译器都支持 VLA
【解决方案3】:

我希望我不会在这里遗漏任何东西,但这是我试图回答“有什么区别......”这个问题。如果我完全偏离了基地,请原谅我,我会更正我的答案,但这里是:

我试着画出你的两个 malloc 中发生的事情,所以我要说的与我手绘的图片相关(手工制作的答案?)

第一个选项:

对于第一个选项,您分配一个大小为 5 int*s 的内存块。 M,它是一个 int** 指向该内存块的开始。

然后,您检查每个内存块(int* 的大小),并在每个块中放入大小为 5 个 int 的内存块的地址。请注意,它们位于内存(堆)的某个随机部分中,该部分有足够的空间来占用 5 个整数的大小。

这是关键 - 它是一个不连续的内存块。因此,如果您将内存视为一个数组,那么您将指向数组中不同的起始位置。

第二个选项

你的第二个 int** 的分配完全一样。但相反,它分配 25 个整数的大小并返回将该数组的地址放置在内存块 M[0] 中。注意:您从未在内存位置 M[1] - M[4] 中放置任何地址。

那么,会发生什么?您有一个连续的 25 个整数块,其地址可在 M[0] 中找到。当您尝试获取 M[1] 时会发生什么?你猜对了——它是空的或包含垃圾值。更重要的是,它是一个不指向分配的内存空间的值,所以你是 Segfault。

【讨论】:

  • 非常感谢.. 很好的解释
【解决方案4】:

如果你想在连续内存中分配一个 5x5 数组,正确的做法是

int rows = 5;
int cols = 5;
int (*M)[cols] = malloc(rows * sizeof(*M));

然后您可以使用普通数组索引访问数组,例如

M[3][2] = 6;

【讨论】:

    【解决方案5】:

    int **M = malloc(5 * sizeof(int *));指为指针分配内存M[i] = malloc(5 * sizeof(int));指为int变量分配内存。

    也许这会帮助您了解发生了什么:

    int **M  = malloc(5 * sizeof(void *));
    /* size of 'void *' and size of 'int *' are the same */
    for (i = 0; i < 5; i++)
    {
        M[i] =  malloc(5 * sizeof(int));
    }
    

    【讨论】:

    • 为了扩展这一点,OP提供的第二个代码示例仅初始化了M的桶0。@GRC提供的代码将桶M[0]分配给M[4]。
    【解决方案6】:

    使用malloc((5*5) * sizeof(int)); 时的另一个小区别。当然是 OP 正在寻找的一个次要问题,但仍然是一个问题。

    以下两项与 2 个操作数的顺序相同,仍会导致乘积使用 size_t 数学。

    #define N 5
    malloc(N * sizeof(int));
    malloc(sizeof(int) * N);
    

    考虑:

    #define N some_large_value
    malloc((N*N) * sizeof(int));
    

    sizeof() 的结果类型是size_t 类型,一个无符号整数类型,那肯定有SIZE_MAX &gt;= INT_MAX,可能大。所以为了避免int溢出不会溢出size_t数学使用

    malloc(sizeof(int) * N * N);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-09-07
      • 2011-12-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-08
      相关资源
      最近更新 更多