The biggest misunderstanding is you are passing answer
by-value to add()
so add()
receives a copy and any changes to answer
in add()
are lost on function return. And since the type is void
you have no chance of making changes to answer
seen back in the caller (main()
here)
To fix the issue, pass the address of answer
to add()
so you operate on the original pointer address when you realloc()
not calloc()
to double the size, e.g.
void add(Statistician *answer, int *SIZE){
void *tmp = realloc(*answer, *SIZE * 2 * sizeof **answer);
if (!tmp) {
perror ("add-realloc-answer");
return;
}
*answer = tmp;
/* optional - zero new memory mimicking calloc() */
memset (*answer + *SIZE, 0, *SIZE * sizeof **answer);
*SIZE *= 2;
}
You call add like:
add(&answer, &SIZE);
(note, SIZE
must be a global int
properly initialized. It should actually be replace in main()
with size_t size = SIZE;
at the beginning and then pass a pointer to size
).
Your confusion is compounded because you typedeffed the pointer, see Is it a good idea to typedef pointers?. Additionally, in C, there is no need to cast the return of malloc
(or calloc
, or realloc
), it is unnecessary. See: Do I cast the result of malloc?
Eliminating The typedef
Of The Pointer
To keep the levels of pointer indirection apparent it is far easier to avoid the typedef
of the pointer and simply use int*
where the type is needed. Making those changes, and changing the use of SIZE
with a local variable size
to ensure any local copies passed as parameters don't collide with a global variable. Adding simple routines to fill each integer and validate every integer before/after add()
is accounted for, a preferred approach would be:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SIZE 128
int *add (int **answer, size_t *size)
{
/* always realloc to a temporary pointer, when realloc fails
* it returns NULL and if you assign to original pointer you
* create a memory leak with loss of your stored data.
*/
void *tmp = realloc(*answer, *size * 2 * sizeof **answer);
if (!tmp) { /* validate every allocation */
perror ("add-realloc-answer");
return NULL;
}
*answer = tmp; /* assign reallocated block to pointer */
/* optional - zero new memory mimicking calloc() */
memset (*answer + *size, 0, *size * sizeof **answer);
*size *= 2; /* update size only on success */
return *answer; /* return pointer indicating success */
}
int main(void) {
int *answer;
size_t size = SIZE;
answer = calloc(size, sizeof *answer); /* allocate */
if (answer == NULL) { /* validate */
perror ("calloc-answer");
return 1;
}
for (size_t i = 0; i < size; i++) /* fill memory */
answer[i] = i;
if (!add(&answer, &size)) /* add and validate return */
fputs ("realloc failed, using original size.\n", stderr);
else
printf ("realloc succeeded -- %zu integers.\n", size);
for (size_t i = size/2; i < size; i++) /* fill new memory */
answer[i] = i;
for (size_t i = 0; i < size; i++) /* validate complete block */
if (i != (size_t)answer[i])
fprintf (stderr, "error: answer[%zu] != %d\n", i, answer[i]);
free (answer); /* free allocated memory */
}
Note: since realloc()
can fail, your function needs a way to communicate success or failure back to the caller. Choose a meaningful return type (int
0/1
or a pointer valid address/NULL
, etc...)
Example Use/Output
$ ./bin/realloc-add
realloc succeeded -- 256 integers.
Memory Use/Error Check
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 ensure 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/realloc-add
==8594== Memcheck, a memory error detector
==8594== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==8594== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==8594== Command: ./bin/realloc-add
==8594==
realloc succeeded -- 256 integers.
==8594==
==8594== HEAP SUMMARY:
==8594== in use at exit: 0 bytes in 0 blocks
==8594== total heap usage: 3 allocs, 3 frees, 2,560 bytes allocated
==8594==
==8594== All heap blocks were freed -- no leaks are possible
==8594==
==8594== For counts of detected and suppressed errors, rerun with: -v
==8594== 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.
Let me know if you have further questions.