Brief overview of my program: it takes in a list of about 500 sets of initial parameters. It then splits those 500 into "chunks" of 50 so it can calculate the 50 and write the results to file, then move on to the next "chunk" of 50, etc. It's a checkpointing system. Anyway, for every "chunk", an array of structs called calc_result
is created to hold 50 results. Each calc_result
consists of a name, an index, and a 2d char array for the actual results. Then work is done and written to file, and the current calc_result
array is freed. Here is the basic program:
typedef struct
{
char* name;
int index;
char** finals;
} calc_results;
int main(int argc, char** argv)
{
//read in sets of initial parameters
int numChunks = 10;
int calcsPerChunk = 50;
for(int i = 0; i < numChunks; i++)
{
calc_result** results = malloc(sizeof(calc_result*)*calcsPerChunk);
for(int j = 0; j < calcsPerChunk; j++)
{
results[j] = malloc(sizeof(calc_result));
results[j]->name = "blah";
results[j]->index = j;
results[j]->finals = malloc(sizeof(char*) * 12); //12 final results
for(int k = 0; k < 12; k++)
{
results[j]->finals[k] = malloc(70); //max string size is 70
}
}
//DO ACTUAL WORK
//WRITE RESULTS TO FILE
//FREE STUFF:
for(int a = 0; a < calcsPerChunk; a++)
{
for(int b = 0; b < 12; b++)
{
free(results[a]->finals[b]);
}
free(results[a]->name);
free(results[a]->finals);
free(results[a]);
}
free(results);
}
}
I'm having trouble with the freeing of the results. The program runs through about 7 of the 10 "chunks", and runs through freeing 1 of the calcsPerChunk
(ie, i = 8
, a = 1
and b = 0
), then it throws an error pointing to the free(results[a]->finals[b])
line. The error is useless: "Program.exe" has triggered a breakpoint." I'm not sure what I'm doing wrong here, can anyone help?
Note: your code has NO array in it, so your Question Title is a misnomer. You are allocating for a pointer-to-pointer to type where you first allocate pointers, and then a block of storage for each object and then you assign the beginning address for each block of storage to a pointer. (Rule: a pointer is not an array, and an array is not a pointer -- though on access an array is converted to a pointer to the first element pursuant to C11 Standard - 6.3.2.1(p3))
Your only problem is attempting to free (results[a]->name);
which is not allocated, and then your failure to validate the return of each allocation. Other than that, you are very very close. You knew what you needed to free
and the order to do it in, the free(results[a]->name);
looks more like a "...duh..."
error than any failure in understanding.
You can also size the type for each allocation by using the dereferenced pointer rather than trying to recall the X-type
for sizeof(X-type)
which can be error-prone in circumstances. For example, instead of:
calc_result** results = malloc(sizeof(calc_result*)*calcsPerChunk);
You can use the sizeof *results
(the dereferenced pointer) to set the size of each object, e.g.
calc_results **results = malloc (sizeof *results * calcsPerChunk);
Note: the '*'
or '**'
generally goes with the pointer and not the type. Why?, semantics and making what the pointer is explicit. For example:
calc_results* a, b, c;
certainly does NOT declare 3-pointers to calc_result
. Instead it declares pointer to calc_result a
and two struct calc_result b, c
. Ensuring the '*'
is on the pointer makes that clear, e.g.
calc_results *a, b, c;
(legal syntax-wise, it makes no difference, the compiler can parse it without any problem -- it's the human-side where the problems tend to occur)
A Quick Example Confirming Your Approach
For testing, you don't need:
int numChunks = 10;
int calcsPerChunk = 50;
(3
and 5
(or anything more than 1
) will do)
Putting together a short example that validates each allocation as well as sizes each allocation by using the dereferenced pointer, (and spits out output for each level just for fun) you could do:
#include <stdio.h>
#include <stdlib.h>
typedef struct {
char *name;
int index;
char **finals;
} calc_results;
int main(void)
{
int numChunks = 3;
int calcsPerChunk = 5;
for(int i = 0; i < numChunks; i++) {
calc_results **results = malloc (sizeof *results * calcsPerChunk);
if (!results) {
perror ("malloc-results");
return 1;
}
for(int j = 0; j < calcsPerChunk; j++)
{
if (!(results[j] = malloc (sizeof *results[j]))) {
perror ("malloc-results[j]");
return 1;
}
results[j]->name = "blah";
results[j]->index = j;
if (!(results[j]->finals=malloc(sizeof *results[j]->finals*12))) {
perror ("malloc-results[j]->finals");
return 1;
}
for(int k = 0; k < 12; k++) {
if (!(results[j]->finals[k] = malloc(70))) {
perror ("malloc-results[j]->finals[k]");
return 1;
}
sprintf (results[j]->finals[k], "grade %d", k+1);
}
}
/* DO ACTUAL WORK */
/* output & free stuff */
printf ("results[%2d]\n", i);
for (int a = 0; a < calcsPerChunk; a++) {
printf (" %s %2d\n", results[a]->name, results[a]->index);
for (int b = 0; b < 12; b++) {
printf (" %s\n", results[a]->finals[b]);
free (results[a]->finals[b]);
}
// free(results[a]->name);
free(results[a]->finals);
free(results[a]);
}
free(results);
}
}
Memory Use/Error Check
Now for the critical part. In any code you write that dynamically allocates memory, you have 2 responsibilities regarding any block of memory allocated: (1) always preserve a pointer to the starting address for the block of memory so, (2) it can be freed when it is no longer needed.
It is imperative that you use a memory error checking program to insure you do not attempt to access memory or write beyond/outside the bounds of your allocated block, attempt to read or base a conditional jump on an uninitialized value, and finally, to confirm that you free all the memory you have allocated.
For Linux valgrind
is the normal choice. There are similar memory checkers for every platform. They are all simple to use, just run your program through it.
$ valgrind ./bin/free_nested_struct
==13663== Memcheck, a memory error detector
==13663== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==13663== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==13663== Command: ./bin/free_nested_struct
==13663==
results[ 0]
blah 0
grade 1
grade 2
grade 3
grade 4
grade 5
grade 6
...
<snip>
==13663==
==13663== HEAP SUMMARY:
==13663== in use at exit: 0 bytes in 0 blocks
==13663== total heap usage: 213 allocs, 213 frees, 14,520 bytes allocated
==13663==
==13663== All heap blocks were freed -- no leaks are possible
==13663==
==13663== For counts of detected and suppressed errors, rerun with: -v
==13663== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Always confirm that you have freed all memory you have allocated and that there are no memory errors.
Look things over and let me know if you have further questions. You really were very close, and I suspect you are actually allocating for name
in your actual code, so more than likely, your code should have worked.