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