简体   繁体   中英

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

In a recent exam question I got this code with following options:

 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]); } for(i = 0; i < 10; i++): { free(mptr[i]); }

C. All of them

The answer is C. But it seems to me that applying free(mptr); would suffice covering the memory leak, and B as well, although I'm less sure of that, can someone explain me why all of them would cause a memory leak? I'm guessing the C options expects that each operation ( A or B ) are applied separatly.

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

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

In my pedantic opinion the correct answer would have to be option A , it creates a memory leak because it deallocates mptr , making mptr[i] pointers inaccessible. They cannot be deallocated afterwards, assuming that the memory is completely inaccessible by other means.

Option B does not lead to memory leak per se , mptr is still accessible after you free mptr[i] pointers. You can reuse it or deallocate it later. Memory leak would only occur if and when you loose access to the memory pointed by mptr .

I believe the question is somewhat ill-formed, if the question was "Which option would you use to correctly deallocate all the memory?" , then yes, option C would be correct.

I do agree that the correct strategy to deallocate all the memory is B + A , albeit A first will cause immediate memory leak whereas B first will allow for later deallocation of mptr , as long as the access to the memory pointed by it is not lost.

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?

The allocation is correct.

//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 for more info.

Let's draw this out:

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

If all you do is free the memory pointed to by mptr , you wind up with this:

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

The allocations for each mptr[i] are not freed. Those are all separate allocations, and each must be freed independently before you free mptr . free does not examine the contents of the memory it's deallocating to determine if there are any nested allocations that also need to be freed. The proper procedure would be to write

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

If all you do is free each mptr[i] but not mptr , you wind up with this:

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

You still have the array of pointers you allocated initially. Now, this isn't a memory leak yet - it only becomes one when you lose track of mptr .

So, these are the rules for memory management in C:

  • Every malloc , calloc , or realloc call must eventually have a corresponding free ;
  • When doing nested allocations, always deallocate in reverse order that you allocated (ie, deallocate each ptr[i] before deallocating ptr );
  • The argument to free must be a pointer returned from a malloc , calloc , or realloc call.

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

This is an example of a "jagged" array, where each "row" can be a different length (which you can't do with a regular 2D array). This can be handy if you're storing (for example) a list of words of all different lengths:

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

If necessary, you can easily resize each "row" without affecting any of the others, and you can easily add more "rows". This looks like a 2D array when you index it - you can access individual elements using mptr[i][j] like any other 2D array - but the "rows" are not contiguous in memory.

Compare this with a "real" 2D array, where all the rows are the same size and laid out contiguously:

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

The main disadvantage is some wasted space. Your array has to be sized for the longest word you want to store. If you have a table of 100 strings, one of which is 100 characters long and the rest 10, then you have a lot of wasted space. You can't have one row that's longer than the others.

The advantage is that the rows are contiguous, so it's easier to "walk" down the array.

Note that you can allocate a regular 2D array dynamically as well:

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

This allocates enough space for a 10x10 array of char in a single allocation , which you can index into like any other 2D array:

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

Since this is a single allocation, you only need a single deallocation:

free( ptr );

free(mptr); just frees the memory allocated for the pointers, but not the memory the pointers point to.

If you free() the memory for the pointers before freeing the memory the pointer to point to, you got no reference anymore to the memory to be pointed to and hence you got a memory leak.


for(i = 0; i < 10; i++): { free(mptr[i]); } for(i = 0; i < 10; i++): { free(mptr[i]); } , on the other side, frees only the memory pointed to but not the pointers. Dependent upon how strictly you see it, you could also consider this as memory leak because the memory for the pointers is not deallocated.

So, dependent upon the point of view and one's own opinion, either A. or C. is correct.


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?

With

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

you allocated memory for the pointers itself to which the pointer to pointer mptr is pointing to.

But pointers need to point to data memory which you can access by using the pointers. The pointers itself can't store any other data than the address of the memory to point to.

Thus, you allocate memory to point to for each pointer by each iteration inside of the for loop.

A pointer always needs a place to point to in order to use it correctly as pointer (Exception: null pointers).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM