【问题标题】:Allocating a pointer with calloc, and then dynamically allocate each cell with malloc = memory leakage?用calloc分配一个指针,然后用malloc=内存泄漏动态分配每个单元格?
【发布时间】:2021-01-01 06:43:51
【问题描述】:

在最近的一个考试问题中,我得到了带有以下选项的代码:

char **mptr, *pt1;
int i;
mptr = calloc(10, sizeof(char*));
for (i=0; i<10; i++)
{
    mptr[i] = ( char *)malloc(10);
}   

以下哪种解除分配策略会导致内存泄漏?

A. free(mptr);

B. for(i = 0; i &lt; 10; i++): { free(mptr[i]); }

C.都是这样的

答案是 C。但在我看来,应用 free(mptr); 就足以覆盖内存泄漏,而 B 也是如此,虽然我不太确定,但有人可以解释一下为什么它们都会导致内存泄漏? 我猜 C 选项期望每个操作( A 或 B )单独应用。

附: 我看不出这段代码的意义,如果您已经使用calloc 分配了内存(并对其进行了初始化),为什么还要为每个单元分配一个循环?我错了?

【问题讨论】:

  • 每个malloc/calloc 都必须有自己的free。所以任何不遵守这条规则的东西都会造成泄漏。
  • 需要先做b再做a。

标签: c pointers memory-management malloc calloc


【解决方案1】:

以下哪种解除分配策略会导致内存泄漏?

在我看来,正确的答案必须是选项A,它会造成内存泄漏,因为它会释放mptr,从而使mptr[i] 指针无法访问。假设内存完全无法通过其他方式访问,则它们不能在之后被释放。

选项 B 不会导致内存泄漏本身,在释放 mptr[i] 指针后,mptr 仍然可以访问。您可以重用它或稍后释放它。只有当您失去对mptr 指向的内存的访问权限时,才会发生内存泄漏。

我认为这个问题有点不正确,如果问题是“你会使用哪个选项来正确释放所有内存?”,那么是的,选项C 是正确的。

我同意正确的策略释放所有内存是B + A,尽管A first 会导致立即内存泄漏,而B first 将允许稍后mptr的释放,只要对它指向的内存的访问不丢失。

我看不出这段代码的意义,如果您已经使用 calloc 分配了内存(并对其进行了初始化),为什么还要为每个单元分配一个循环?我有错吗?

分配正确。

//pointer to pointer to char, has no access to any memory
char **mptr;

//allocates memory for 10 pointers to char
mptr = calloc(10, sizeof(char*));

//allocates memory for each of the 10 mptr[i] pointers to point to
for (i = 0; i < 10; i++)
{
    mptr[i] = malloc(10); //no cast needed, #include <stdlib.h>
}

Check this thread 了解更多信息。

【讨论】:

  • 如果选项 B 是“您的取消分配策略”,那么它仍然是泄漏,因为您没有取消分配所有内容。
  • @user253751,正确,但问题是哪个选项会导致内存泄漏,而取消分配 mptr[i] 不会导致内存泄漏。
  • @user253751,如果问题是您将使用哪一个来正确释放所有内存,那么是的,我会回答 C。
  • 这段代码有用吗? (检查我编辑的问题,我添加了一个关于此事的更多内容)
  • @B.Castarunza,是的,分配是正确的,我留下了关于此事的链接。
【解决方案2】:

让我们把它画出来:

      char **        char *                 char
      +---+          +---+                  +---+---+     +---+
mptr: |   | -------->|   | mptr[0] -------->|   |   | ... |   |
      +---+          +---+                  +---+---+     +---+
                     |   | mptr[1] ------+  
                     +---+               |  +---+---+     +---+
                      ...                +->|   |   | ... |   |
                     +---+                  +---+---+     +---+
                     |   | mptr[9] ----+
                     +---+             |    +---+---+     +---+
                                       +--->|   |   | ... |   |
                                            +---+---+     +---+

如果你所做的只是释放mptr 指向的内存,你会得到这样的结果:

      char **                               char
      +---+                                 +---+---+     +---+
mptr: |   |                                 |   |   | ... |   |
      +---+                                 +---+---+     +---+
                       
                                            +---+---+     +---+
                                            |   |   | ... |   |
                                            +---+---+     +---+
                      
                                            +---+---+     +---+
                                            |   |   | ... |   |
                                            +---+---+     +---+

每个mptr[i] 的分配没有被释放。这些都是单独的分配,并且必须在您释放mptr 之前单独释放。 free 不会检查它正在释放的内存的内容,以确定是否有任何嵌套分配也需要释放。正确的程序是写

for ( int i = 0; i < 10; i++ )
  free( mptr[i] );
free( mptr );

如果你所做的只是释放每个 mptr[i] 而不是 mptr,你会得到这个:

      char **        char *                 
      +---+          +---+                  
mptr: |   | -------->|   | mptr[0] 
      +---+          +---+                  
                     |   | mptr[1]
                     +---+            
                      ...   
                     +---+ 
                     |   | mptr[9] 
                     +---+             

您仍然拥有最初分配的指针数组。现在,这还不是内存泄漏 - 只有当您忘记了 mptr 时,它才会变成一个。

所以,这些是 C 中的内存管理规则:

  • 每个malloccallocrealloc 调用最终都必须有一个对应的free
  • 在进行嵌套分配时,始终按照您分配的相反顺序解除分配(即,在解除分配 ptr 之前解除分配每个 ptr[i]);
  • free 的参数必须是从malloccallocrealloc 调用返回的指针。

附:我看不出这段代码的意义,如果您已经使用calloc 分配了内存(并对其进行了初始化),为什么还要为每个单元分配一个循环?我有错吗?

这是一个“锯齿状”数组的示例,其中每个“行”可以是不同的长度(这是常规二维数组无法做到的)。如果您要存储(例如)所有不同长度的单词列表,这会很方便:

char **        char *        char
+---+          +---+         +---+---+---+---+
|   | -------->|   |-------->|'f'|'o'|'o'| 0 |
+---+          +---+         +---+---+---+---+
               |   | -----+  
               +---+      |  +---+---+---+---+---+---+---+
               |   | ---+ +->|'b'|'l'|'u'|'r'|'g'|'a'| 0 |
               +---+    |    +---+---+---+---+---+---+---+
                ...     |
                        |    +---+---+---+---+---+---+
                        +--->|'h'|'e'|'l'|'l'|'o'| 0 |
                             +---+---+---+---+---+---+

如有必要,您可以轻松调整每个“行”的大小而不会影响其他任何“行”,并且您可以轻松添加更多“行”。这个看起来当你索引它时就像一个二维数组 - 你可以像任何其他二维数组一样使用mptr[i][j] 访问单个元素 - 但“行”在内存中并不连续。

将此与“真实”二维数组进行比较,其中所有行的大小相同且连续布置:

+---+---+---+---+---+---+---+
|'f'|'o'|'o'| 0 | ? | ? | ? |
+---+---+---+---+---+---+---+
|'b'|'l'|'u'|'r'|'g'|'a'| 0 |
+---+---+---+---+---+---+---+
|'h'|'e'|'l'|'l'|'o'| 0 | ? |
+---+---+---+---+---+---+---+

主要缺点是一些浪费的空间。您的数组的大小必须适合您要存储的最长单词。如果您有一个包含 100 个字符串的表,其中一个长度为 100 个字符,其余 10 个字符长,那么您将浪费大量空间。一行不能比其他行长。

优点是行是连续的,因此更容易“遍历”数组。

请注意,您也可以动态分配常规二维数组:

char (*ptr)[10] = calloc( 10, sizeof *ptr );

这为 char 的 10x10 数组分配了足够的空间在单个分配中,您可以像任何其他二维数组一样对其进行索引:

strcpy( ptr[0], "foo" );
ptr[0][0] = 'F';

由于这是一次分配,因此您只需要一次解除分配:

free( ptr );

【讨论】:

  • 第二条规则实际上并不是一条规则。在这种特殊情况中,您必须这样做,否则您的程序将不知道它应该释放哪些内存。但 C 不在乎。
【解决方案3】:

free(mptr); 只是释放分配给指针的内存,而不是指针指向的内存。

如果您在释放指针指向的内存之前free() 指针的内存,那么您将不再引用要指向的内存,因此您会发生内存泄漏。


for(i = 0; i &lt; 10; i++): { free(mptr[i]); },另一方面,只释放指向的内存而不释放指针。根据您对它的严格程度,您也可以将其视为内存泄漏,因为指针的内存没有被释放。

因此,根据观点和个人意见,A. 或 C. 都是正确的。


我看不出这段代码的意义,如果你已经用calloc 分配了内存(并对其进行了初始化),为什么还要为每个单元分配一个循环?

mptr = calloc(10, sizeof(char*));

您为指针mptr 所指向的指针 本身分配了内存。

指针需要指向数据存储器,您可以使用指针访问它。除了要指向的内存地址之外,指针本身不能存储任何其他数据。

因此,您通过for 循环内的每次迭代为每个指针分配内存。

指针总是需要一个指向的地方才能正确地用作指针(例外:空指针)。

【讨论】:

  • 我的观点我能理解,不过我觉得导师应该更严谨一些,尤其是考试的时候。
猜你喜欢
  • 2016-12-11
  • 1970-01-01
  • 2013-12-21
  • 2014-07-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-10
  • 2016-09-12
  • 1970-01-01
相关资源
最近更新 更多