用calloc分配一个指针,然后用malloc动态分配每个cell=内存泄漏?

Allocating a pointer with calloc, and then dynamically allocate each cell with malloc = memory leakage?

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

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

Which of the following de-allocation strategies creates a memory leakage?

A. free(mptr);

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

C. All of them

答案是 C。但在我看来,应用 free(mptr); 足以弥补内存泄漏,还有 B,虽然我不太确定,但有人能解释一下为什么所有的它们会导致内存泄漏吗? 我猜 C 选项期望每个操作( A 或 B )分别应用。

P.S。 我不明白这段代码的意义所在,如果您已经使用 calloc 分配了内存(并对其进行了初始化),为什么还要为每个单元格分配一个循环?我相信是错的吗?

Which of the following de-allocation strategies creates a memory leakage?

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

选项 B 不会导致内存泄漏 本身 ,释放 mptr[i] 指针后 mptr 仍然可以访问。您可以重用它或稍后释放它。仅当您失去对 mptr.

指向的内存的访问时,才会发生内存泄漏

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

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

I don't see the point of this code, if you already have allocated memory with calloc (and initialized it) why would you go as far as to allocate each cell with a cycle? Am I wrong to believe that?

分配正确。

//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 了解更多信息。

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

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


另一方面,

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

因此,取决于观点和个人意见,A. 或 C. 是正确的。


I don't see the point of this code, if you already have allocated memory with calloc (and initialized it) why would you go as far as to allocate each cell with a cycle?

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

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

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

因此,您在 for 循环内的每次迭代中分配内存以指向每个指针。

指针总是需要一个指向的位置才能将其正确用作指针(例外:空指针)。

让我们把它画出来:

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

如果您所做的只是释放 mptr 指向的内存,您将得到以下结果:

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

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

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 调用返回的指针。

P.S. I don't see the point of this code, if you already have allocated memory with calloc (and initialized it) why would you go as far as to allocate each cell with a cycle? Am I wrong to believe that?

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

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 | ? |
+---+---+---+---+---+---+---+

主要缺点是有些浪费space。您的数组的大小必须适合您要存储的最长单词。如果你有一个 table 的 100 个字符串,其中一个是 100 个字符,其余的是 10,那么你有很多浪费 space。一行不能比其他行长。

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

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

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

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

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

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

free( ptr );