2

I have an Array of Strings inside a Struct that looks like this

#define ID_LEN 20

struct Person{
char name[ID_LEN];
int num_items;
char **items;
};

int main(){
int num_persons = 10;
struct Person *ptr[num_persons];

I start with an array of 10 persons. Initially all persons have 0 items so their list of items is an malloc(0).

for(int i = 0; i < num_persons; i++){
        ptr[i] = (struct Person *) malloc(sizeof(struct Person) + 1);
        ptr[i]->num_items = 0;
        ptr[i]->items = malloc(0 * sizeof(char*));
}

At some point I want to name those persons and add them some items like this.

strcpy(ptr[0]->name, "John");
ptr[0]->num_items = 1;
ptr[0]->items = (char **)realloc(ptr[0]->items, ptr[0]->num_items * sizeof(char*));
strcpy(ptr[0]->items[0], "pencil");


printf("Name: %s\n", ptr[0]->name);
printf("Number of items: %d\n", ptr[0]->num_items);
for(int i = 0; i < ptr[0]->num_items; i++){
    printf("Item %d is %s\n", i, ptr[0]->items[i]);
}

I'm getting segmentation fault: 11. I'm not sure if I have done realloc correctly or not.

Danick
  • 135
  • 2
  • 9

4 Answers4

2

You have several problems here

First your ID_LEN

nowhere do you check if by copying to name you surpass the ID_LEN

so instead of

strcpy(ptr[0]->name, "John"); 

use

strcpy_s(ptr[0]->name,sizeof(ptr[0]->name),"John or whatever"); // C11

You allocate

ptr[0]->items = (char **)realloc(ptr[0]->items, ptr[0]->num_items * sizeof(char*))

but

you should not cast return value of malloc/realloc (search on web for explanation, it has been described ad nauseum)

when using realloc, you should first check the return value, it may fail.

char** tmp = realloc(ptr[0]->items, ptr[0]->num_items * sizeof(char*))
if (tmp != NULL)
{
  ptr[0]->items = tmp;
}
else
{
  abort();
}

The memory realloc returns is partly uninitialized (old pointers remain but new ones are uninitialized. In your case you did not have any previous pointers so that one items[0] is uninitialized.

so when you do

strcpy(ptr[0]->items[0], "pencil");

it will fail since items[0] is pointing to some arbitrary memory location.

after you realloc pointers you need initialize them to point to a memory large enough to accommodate your string

E.g.

ptr[0]->items[0] = strdup("pencil");  // does malloc then copies string

It is not so efficient to use realloc everytime you need to add one new item, instead allocate a bunch of items but set the ones you are not using to NULL then keep track of how many are left, once they run out allocate another bunch

AndersK
  • 35,813
  • 6
  • 60
  • 86
1

All the problem in your code revolves around using memory which is not allocated. Consider

 ptr[0]->items = (char **)realloc(ptr[0]->items, ptr[0]->num_items * sizeof(char*));//line 1
 strcpy(ptr[0]->items[0], "pencil");//line 2

In line 1 you have allocated memory to hold ptr[0]->num_items number of pointers to c strings.But you have not actually allocated memory to store c strings i.e. those pointers are not pointing to actual memory. When in line 2 you try to access ptr[0]->items[0] its just char* there is no memory allocated to it. I added the below line before line 2 and your code worked fine.

 for(int i=0;i<ptr[0]->num_items;i++)
      ptr[0]->items[i]=malloc(sizeof(char)*10);
Gaurav Sehgal
  • 7,422
  • 2
  • 18
  • 34
1

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.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
0

You are allocating for the list of items, but you are not allocating space for each individual item string. Your

strcpy(ptr[0]->items[0], "pencil");

will fail.

ChiralMichael
  • 1,214
  • 9
  • 20