In addition to what the other answers provide, there are several other considerations relevant to what you are doing. The first, don't 0
allocate anything. That is a holdover from days gone by and not necessary.
Next, following your static declaration of a variable length array of num_persons
pointers to type struct Person
, you must decide whether you will allocate storage for all your pointers at once, or whether you will allocate storage for the struct, only when adding a person. That will have implications when you split your code up into functions.
Since the remainder of your data structure will be allocated dynamically, you must validate each allocation succeeds before copying/initializing, whatever.
When you allocate your empty structure, take a close look at calloc
instead of malloc
. This is one instance where the default initialization provided by calloc
can help.
The remainder of your task is just an accounting problem -- that is accounting for which members require allocation, the current state and size of each allocation, and then finally to free the memory you have allocated when it is no longer needed.
Take for example the code you might want for adding a person with an item to your array of pointers. To validate the addition, you will need to know that ptr
is a valid pointer and whether you have reached your num_persons
limit before you begin to allocate memory. You next must allocate storage for a struct Person
and assign the address for that block to ptx[X]
(for whatever index X
you are working with).
Once allocated, you can initialize defaults (or if calloc
was used, know that all values were initialized to 0/NULL
). Following that you can copy name
(limited to ID_LEN
) and allocate pointers and storage for each item you wish to hold in your pointer-to-pointer-to-char* items
(which you will handle similar to ptr
accept that the pointers for items
are allocated as well) Note there is no need to realloc (items,...
at this point as this is the first time items
is allocated.
If you put all of that together, you can come up with a function to add a person together with their first item similar to the following, where max
is the maximum number of persons allowed, nm
is the name, itm
is the item, and idx
is a pointer to the current index for that person:
/* add person with item */
struct person *addpwitem (struct person **ptr, int max, char *nm,
char *itm, int *idx)
{
if (!ptr || *idx + 1 == max) return NULL;
int i = *idx;
/* allocate storage for struct */
if (!(ptr[i] = calloc (1, sizeof **ptr))) {
fprintf (stderr, "error: ptr[%d], virtual memory exhausted.\n", i);
return NULL;
}
strncpy (ptr[i]->name, nm, ID_LEN); /* copy name */
/* allocate/validate pointer to char* ptr[i]->items */
if (!(ptr[i]->items =
malloc (ptr[i]->num_items + 1 * sizeof *(ptr[i]->items)))) {
fprintf (stderr, "error: items*, virtual memory exhausted.\n");
return NULL;
}
/* allocate/validate memory for ptr[num_items]->items[num_items] */
if (!(ptr[i]->items[ptr[i]->num_items] = strdup (itm))) {
fprintf (stderr, "error: items, virtual memory exhausted.\n");
return NULL;
}
ptr[i]->num_items++;
(*idx)++;
return ptr[i];
}
(note: you must assign the return of the function to ptr[X]
in the calling function. Also note, C-style recommends all lower-case names, which is what you see above, leave CamelCase names to C++)
Once you have a person added, you will want the ability to add items to the list of items associated with that person. This is where realloc
comes in to allow you to resize the number of pointers-to-items for that person. You can do something similar to add person, but the index you pass no longer needs to be a pointer as it will not be updated within the function. e.g.
/* add item to person at index 'i' */
struct person *additem (struct person **ptr, int i, char *itm)
{
if (!ptr) return NULL;
void *tmp;
/* allocate/realloc/validate pointer to char* ptr[i]->items */
if (!(tmp = realloc (ptr[i]->items,
(ptr[i]->num_items + 1) * sizeof *(ptr[i]->items)))) {
fprintf (stderr, "error: items*, virtual memory exhausted.\n");
return NULL;
}
ptr[i]->items = tmp; /* assign tmp on successful realloc */
/* allocate/validate memory for ptr[num_items]->items[num_items] */
if (!(ptr[i]->items[ptr[i]->num_items] = strdup (itm))) {
fprintf (stderr, "error: items, virtual memory exhausted.\n");
return NULL;
}
ptr[i]->num_items++;
return ptr[i];
}
You are now able to add both persons and items to the list, but need a way to iterate over all values in the list if you are going to make use of them. Whether searching, printing, or freeing memory, all iteration functions will have similar form. To print all persons and items you could do something similar to the following:
/* print the list of persons and items */
void prndetail (struct person **ptr, int idx)
{
if (!ptr || !*ptr) return;
int i, p;
for (p = 0; p < idx; p++) {
printf (" %-20s %2d", ptr[p]->name, ptr[p]->num_items);
for (i = 0; i < ptr[p]->num_items; i++)
printf ("%s%s", i ? ", " : " ", ptr[p]->items[i]);
putchar ('\n');
}
}
In any code your write that dynamically allocates memory, you have 2 responsibilites regarding any block of memory allocated: (1) always preserves a pointer to the starting address for the block of memory so, (2) it can be freed when it is no longer needed. When you are done with your list, you will need to free
it. Similar to printing, it can be done as follows:
/* free all allocated memory */
void deletelist (struct person **ptr, int idx)
{
if (!ptr || !*ptr) return;
int i, p;
for (p = 0; p < idx; p++) {
for (i = 0; i < ptr[p]->num_items; i++)
free (ptr[p]->items[i]);
free (ptr[p]->items);
free (ptr[p]);
}
// free (ptr); /* if array of pointers allocated, not static */
}
That's really all there is to the accounting lesson. If you keep track of each part of the data structure you are using, managing the memory is straight forward. Putting all the pieces together in a short example, you could do something like:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
enum { NPER = 10, ID_LEN = 20 };
struct person {
char name[ID_LEN];
int num_items;
char **items;
};
/* add person + item - to add both person and item at once */
struct person *addpwitem (struct person **ptr, int max, char *nm,
char *itm, int *idx);
/* add item to existing person */
struct person *additem (struct person **ptr, int i, char *itm);
void prndetail (struct person **ptr, int idx);
void deletelist (struct person **ptr, int idx);
int main (void) {
int idx = 0;
struct person *ptr[NPER];
/* allocate storage for struct, add person + item */
if (!(ptr[idx] = addpwitem (ptr, NPER, "John", "pencils", &idx))) {
fprintf (stderr, "error: adding ptr[%d] failed.\n", idx);
return 1;
}
printf ("\nadded John:\n");
prndetail (ptr, idx); /* print contents of persons & items */
additem (ptr, idx - 1, "pens"); /* add next item */
printf ("\nadded item 'pens' for John:\n");
prndetail (ptr, idx); /* print contents of persons & items */
/* add next person + item */
if (!(ptr[idx] = addpwitem (ptr, NPER, "Martha", "paper", &idx))) {
fprintf (stderr, "error: adding ptr[%d] failed.\n", idx);
return 1;
}
printf ("\nadded Martha:\n");
prndetail (ptr, idx); /* print contents of persons & items */
deletelist (ptr, idx); /* free all allocated memory */
return 0;
}
/* add person with item */
struct person *addpwitem (struct person **ptr, int max, char *nm,
char *itm, int *idx)
{
if (!ptr || *idx + 1 == max) return NULL;
int i = *idx;
/* allocate storage for struct */
if (!(ptr[i] = calloc (1, sizeof **ptr))) {
fprintf (stderr, "error: ptr[%d], virtual memory exhausted.\n", i);
return NULL;
}
strncpy (ptr[i]->name, nm, ID_LEN); /* copy name */
/* allocate/validate pointer to char* ptr[i]->items */
if (!(ptr[i]->items =
malloc (ptr[i]->num_items + 1 * sizeof *(ptr[i]->items)))) {
fprintf (stderr, "error: items*, virtual memory exhausted.\n");
return NULL;
}
/* allocate/validate memory for ptr[num_items]->items[num_items] */
if (!(ptr[i]->items[ptr[i]->num_items] = strdup (itm))) {
fprintf (stderr, "error: items, virtual memory exhausted.\n");
return NULL;
}
ptr[i]->num_items++;
(*idx)++;
return ptr[i];
}
/* add item to person at index 'i' */
struct person *additem (struct person **ptr, int i, char *itm)
{
if (!ptr) return NULL;
void *tmp;
/* allocate/realloc/validate pointer to char* ptr[i]->items */
if (!(tmp = realloc (ptr[i]->items,
(ptr[i]->num_items + 1) * sizeof *(ptr[i]->items)))) {
fprintf (stderr, "error: items*, virtual memory exhausted.\n");
return NULL;
}
ptr[i]->items = tmp; /* assign tmp on successful realloc */
/* allocate/validate memory for ptr[num_items]->items[num_items] */
if (!(ptr[i]->items[ptr[i]->num_items] = strdup (itm))) {
fprintf (stderr, "error: items, virtual memory exhausted.\n");
return NULL;
}
ptr[i]->num_items++;
return ptr[i];
}
/* print the list of persons and items */
void prndetail (struct person **ptr, int idx)
{
if (!ptr || !*ptr) return;
int i, p;
for (p = 0; p < idx; p++) {
printf (" %-20s %2d", ptr[p]->name, ptr[p]->num_items);
for (i = 0; i < ptr[p]->num_items; i++)
printf ("%s%s", i ? ", " : " ", ptr[p]->items[i]);
putchar ('\n');
}
}
/* free all allocated memory */
void deletelist (struct person **ptr, int idx)
{
if (!ptr || !*ptr) return;
int i, p;
for (p = 0; p < idx; p++) {
for (i = 0; i < ptr[p]->num_items; i++)
free (ptr[p]->items[i]);
free (ptr[p]->items);
free (ptr[p]);
}
// free (ptr); /* if array of pointers allocated */
}
Example Use/Output
$ ./bin/struct_p2p2c
added John:
John 1 pencils
added item 'pens' for John:
John 2 pencils, pens
added Martha:
John 2 pencils, pens
Martha 1 paper
Memory Error Check
It is imperative that you use a memory error checking program to insure you haven't written beyond/outside your allocated block of memory, attempted to read or base a jump on an unintitialized value and finally to confirm that you have freed all the memory you have allocated.
It is simple to do:
$ valgrind ./bin/struct_p2p2c
==7618== Memcheck, a memory error detector
==7618== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==7618== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==7618== Command: ./bin/struct_p2p2c
==7618==
added John:
John 1 pencils
added item 'pens' for John:
John 2 pencils, pens
added Martha:
John 2 pencils, pens
Martha 1 paper
==7618==
==7618== HEAP SUMMARY:
==7618== in use at exit: 0 bytes in 0 blocks
==7618== total heap usage: 8 allocs, 8 frees, 115 bytes allocated
==7618==
==7618== All heap blocks were freed -- no leaks are possible
==7618==
==7618== For counts of detected and suppressed errors, rerun with: -v
==7618== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 1 from 1)
Always confirm All heap blocks were freed -- no leaks are possible and equally important ERROR SUMMARY: 0 errors from 0 contexts.
Look over this and the rest of the answers. There are many good suggestions between them. Let us know if you have further questions.