1

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]); }

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.

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?

B.Castarunza
  • 135
  • 12

3 Answers3

3

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.

anastaciu
  • 23,467
  • 7
  • 28
  • 53
  • If option B is "your de-allocation strategy" then it's still a leak because you didn't deallocate everything. – user253751 Sep 14 '20 at 14:25
  • @user253751, correct, but the question is which option causes a memory leak, and deallocating `mptr[i]` does not lead to memory leaks. – anastaciu Sep 14 '20 at 14:27
  • 1
    @user253751, if the question was which one would you use to correctly deallocate all the memory, then yes, I would answer C. – anastaciu Sep 14 '20 at 14:30
  • Would this code ever be useful? (Check my edited question, I added a P.s. with more on the matter) – B.Castarunza Sep 14 '20 at 14:36
  • @B.Castarunza, yes, the allocation is correct, I left a link about the matter. – anastaciu Sep 14 '20 at 14:39
  • @anastaciu The question *is* which one causes a memory leak if it's your "deallocation strategy" i.e. if it's the way you deallocate this memory. Both cause memory leaks, if they are the way you deallocate this memory. They are both valid ways to deallocate *some of* this memory... – user253751 Sep 14 '20 at 16:41
  • @user253751, again, I agree, the correct strategy is B then A. maybe I'm being too strict in my interpretation, what I mean to say is that if you deallocate `mptr` you will immediately lose access to any of the memory allocated to the `mptr[i]` pointers, you can no longer deallocate that, whereas if you deallocate `mptr[i]` pointers only, it's not an immediate memory leak because you can still deallocate the memory pointed by `mptr` at some point, of course you must do it, so I can accept `C`as a correct answer. – anastaciu Sep 14 '20 at 19:36
  • @anastaciu If you're being pedantic, then even in case A, we don't know that the pointers in the array weren't saved somewhere else, so A isn't a memory leak either. – user253751 Sep 15 '20 at 10:50
  • @user253751, yes that's correct, and yes I'm surely being pedantic, I need to scale it down a bit, I added that to the answer since I'm scaling it back in the future, not right now :) – anastaciu Sep 15 '20 at 12:25
2

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 (i.e., deallocate each ptr[i] before deallocating ptr);
  • The argument to free must be a pointer returned from a malloc, calloc, or realloc call.

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?

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 );
John Bode
  • 119,563
  • 19
  • 122
  • 198
  • The second rule isn't actually a rule. In *this particular case* you have to do that or else your program won't know which memory it's supposed to free. But C doesn't care. – user253751 Sep 14 '20 at 16:42
1

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]); }, 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).

  • I can understand the point of view, I think the tutor should be more rigorous though, especially if it is some kind of exam. – anastaciu Sep 14 '20 at 14:58