2

I am trying to make a dynamic array of structs, and I can successfully add one struct to it. But any more structs I add cause a segmentation fault. Here is my code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define PEOPLE_BLOCK 4

struct Person {
    char *first_name;
    char *last_name;
    unsigned int age;
};

int add_person(struct Person **people, size_t *people_size, size_t *population, struct Person p) {

    if ((sizeof(struct Person) * *population) > *people_size) {
        return -1;
    }

    if ((sizeof(struct Person) * (*population + 1)) >= *people_size) {
        *people_size = *people_size + sizeof(struct Person) * PEOPLE_BLOCK;
        *people = realloc(*people, *people_size);
        if (!*people) {
            return -1;
        }
    }

    *people[*population] = p;
    ++*population;

    return 0;
}

int main(int argc, char const *argv[]) {

    size_t population;
    size_t people_size;
    struct Person *people, timn, batman;

    population = 0;
    people_size = sizeof(struct Person) * PEOPLE_BLOCK;
    people = malloc(people_size);

    timn.first_name = "Timn";
    timn.last_name = "Timothy";
    timn.age = 38;
    add_person(&people, &people_size, &population, timn);

    printf("Person 0's first name: %s\n", people[0].first_name);

    batman.first_name = "Bat";
    batman.last_name = "Man";
    batman.age = 42;
    add_person(&people, &people_size, &population, batman);

    printf("Person 1's first name: %s\n", people[1].first_name);

    free(people);

    return 0;
}

I'd appreciate any help on why this is happening, thanks!

4
  • 1
    What is your purpose for the leading if block in add_person ? And similarly, the point of the return value of the same function if you never check it on the caller side? Commented May 1, 2015 at 17:56
  • Now that I notice that leading if, that's from something I changed and forgot to take out. And eventually I'd check the return value once I got it to work Commented May 1, 2015 at 18:04
  • 1
    You may not want to directly *people = realloc(*people, *people_size);. What if realloc fails? realloc returns NULL and you lose your pointer to all data. Better to create a tmp pointer and tmp = realloc(*people, *people_size);, then if (tmp) *people = tmp;. Commented May 1, 2015 at 18:27
  • @Addison If the question was answered mark at least one answer as the one so this post is technically closed. Commented May 1, 2015 at 19:57

4 Answers 4

4

The problem resides with this line :

*people[*population] = p;

Change it to:

(*people)[*population] = p;

Why are the parenthesis requried?

The compiler has rules of operator precedence. When applying them, it sees your code as this:

*(people[*population]) = p;

which is not what you intended. Given a pointer-to-pointer Type **pp,

*pp[n] = value;

means "take the n'th pointer starting at pp, and assign value at the location dereferenced from the address that pointer holds. In other words, it means essentially this:

Type *p = pp[n];
*p = value;

What you really want is something that does this:

Type *p = *pp;
p[n] = value;

and that is what (*pp)[n], distinguishing the dereference of the pointer to pointer, gives you. Without that, you're using an invalid pointer, leading to your fault.

Sign up to request clarification or add additional context in comments.

Comments

1

Not sure whether this answer will help, but anyway. I don't understand your code, what you are trying to do.

You directly use the number of elements, a pointer to the first person, and the maximum number of elements. You'll probably have a lot of problems passing that all around.

You're storing literal strings directly in your structs, which means that in a real case (using no literals) that would result in memory leaks.

Here is my take. I've made PEOPLE_BLOCK smaller for testing reasons. Hope this helps.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define PEOPLE_BLOCK 2

typedef struct _Person {
    char *first_name;
    char *last_name;
    unsigned int age;
} Person;

typedef struct _VectorPeople {
    Person * people;
    size_t num;
    size_t max;
} VectorPeople;

void init(VectorPeople *v)
{
    v->max = PEOPLE_BLOCK;
    v->num = 0;
    v->people = (Person *) malloc( sizeof(Person) * v->max );
}

void clear(VectorPeople *v)
{
    // Clear persons
    Person * it = v->people;
    while( ( it - v->people ) < v->num ) {
        free( it->first_name );
        free( it->last_name );

        ++it;
    }

    // Clear vector
    v->max = v->num = 0;
    free( v->people );
    v->people = NULL;
}

void add(VectorPeople *v, Person *p)
{
    // Reserve
    if ( v->num >= v->max ) {
        v->max += PEOPLE_BLOCK;

        // Realloc
        v->people = realloc( v->people, v->max * sizeof(Person) );
        if ( v->people == NULL ) {
            exit( -1 );
        }
    }

    // Copy strings
    p->first_name = strdup( p->first_name );
    p->last_name = strdup( p->last_name );

    // Insert
    v->people[ ( v->num )++ ] = *p;
}

int main(int argc, char const *argv[]) {

    VectorPeople vp;
    Person timn;
    Person batman;
    Person bond;
    Person superman;

    init( &vp );

    timn.first_name = "Timn";
    timn.last_name = "Timothy";
    timn.age = 38;
    add( &vp, &timn );

    batman.first_name = "Batn";
    batman.last_name = "Man";
    batman.age = 42;
    add( &vp, &batman );

    bond.first_name = "James";
    bond.last_name = "Bond";
    bond.age = 45;
    add( &vp, &bond );

    superman.first_name = "Super";
    superman.last_name = "Man";
    superman.age = 45;
    add( &vp, &superman );

    int i = 0;
    for(; i < vp.num; ++i ) {
        printf( "Person: %s, %s.\n", vp.people[ i ].last_name, vp.people[ i ].first_name );
    }
    clear( &vp );
    return 0;
}

1 Comment

Thanks! I'm gonna have to try this approach
1

There were a number of errors in your code. One thing to keep in mind, when you dynamically allocate memory, you are responsible for keeping track of it and freeing it when you no longer need it (otherwise, you will leak memory like a sieve).

In your code, you attempt to create an array of structs holding pointer to an array of characters. The char * pointers are NOT allocated and cannot simply be assigned in the manner you attempt. strdup can help, but you have just allocated memory, so free it when you are done with it.

Attempting to allocate an array of structs with varying (unknown) lengths of first_name and last_name requires that you keep track of every allocation. In some sense, you are better off declaring people as pointer to pointer to Person This allows iteration over your people without having to store the population somewhere allowing you to iterate until the first NULL pointer is encountered.

Likewise, creating a typedef to your struct can greatly cut down on the number of times you write sizeof (struct Person). It keeps the code clean and helps you think though the pointer haze.

Here is an example using a pointer-to-pointer-to-struct of what I think you intended to do. It is followed below by an implementation using only a pointer to struct. Evaluate both and decide which implementation you prefer:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXPOP 128

typedef struct {
    char *first_name;
    char *last_name;
    unsigned char age;
} Person;

Person *add_person (Person ***ppl, Person p, size_t *pop, size_t *max);
Person **realloc_person (Person **ppl, size_t *n);
void free_person (Person *p);
void free_person_names (Person *p);

int main (void) {

    size_t population = 0;
    size_t maxp = MAXPOP;
    size_t i = 0;
    Person timn, batman;
    Person **people = calloc (MAXPOP, sizeof *people);

    if (!people) {
        fprintf (stderr, "error: virtual memory exhausted.\n");
        return 1;
    }

    timn.first_name = strdup ("Timn");
    timn.last_name = strdup ("Timothy");
    timn.age = 38;
    add_person (&people, timn, &population, &maxp);
    free_person_names (&timn);

    printf("\nPerson 0\n first name: %s\n last name : %s\n age : %hhu\n",
            people[0]->first_name, people[0]->last_name, people[0]->age);

    batman.first_name = strdup ("Bat");
    batman.last_name = strdup ("Man");
    batman.age = 42;
    add_person (&people, batman, &population, &maxp);
    free_person_names (&batman);

    printf("\nPerson 1\n first name: %s\n last name : %s\n age : %hhu\n",
            people[1]->first_name, people[1]->last_name, people[1]->age);

    for (i = 0; i < population; i++)
        free_person (people[i]);
    free (people);

    return 0;
}

/* add a person to an array of pointers to Person */
Person *add_person (Person ***ppl, Person p, size_t *pop, size_t *max)
{
    if (*pop == *max)
        *ppl = realloc_person (*ppl, max);

    if (!((*ppl)[*pop] = malloc (sizeof ***ppl)))
        return NULL;

    size_t i = (*pop)++;
    (*ppl)[i]-> first_name = strdup (p.first_name);
    (*ppl)[i]-> last_name = strdup (p.last_name);
    (*ppl)[i]-> age = p.age;

    return (*ppl)[i];
}

/* realloc an array of pointers to Person setting memory to 0. */
Person **realloc_person (Person **ppl, size_t *n)
{
    Person **tmp = realloc (ppl, 2 * *n * sizeof *ppl);
    if (!tmp) {
        fprintf (stderr, "Error: struct reallocation failure.\n");
        // return NULL;
        exit (EXIT_FAILURE);
    }
    ppl = tmp;
    memset (ppl + *n, 0, *n * sizeof *ppl); /* memset new ptrs 0 */
    *n *= 2;

    return ppl;
}

/* free memory for a Person */
void free_person (Person *p)
{
    if (!p) return;
    if (p->first_name) free (p->first_name);
    if (p->last_name) free (p->last_name);
    free (p);
}

/* free only names of Person (for temp structs) */
void free_person_names (Person *p)
{
    if (!p) return;
    if (p->first_name) free (p->first_name);
    if (p->last_name) free (p->last_name);
}

Note: updated to correct ppl start address on reallocation.


Using only Array of Person

While not inherently different than using a pointer to pointer to Person using a simple pointer to Person eliminates the ability to iterate over your array until a NULL or (empty) pointer is encountered. The following is an implementation of the same code using only an array of Person:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXPOP 128

typedef struct {
    char *first_name;
    char *last_name;
    unsigned char age;
} Person;

Person *add_person (Person **ppl, Person p, size_t *pop, size_t *max);
Person *realloc_person (Person *ppl, size_t *n);
void free_person_names (Person p);

int main (void) {

    size_t population = 0;
    size_t maxp = MAXPOP;
    size_t i = 0;
    Person timn, batman;
    Person *people = calloc (MAXPOP, sizeof *people);

    if (!people) {
        fprintf (stderr, "error: virtual memory exhausted.\n");
        return 1;
    }

    timn.first_name = strdup ("Timn");
    timn.last_name = strdup ("Timothy");
    timn.age = 38;
    add_person (&people, timn, &population, &maxp);
    free_person_names (timn);

    printf("\nPerson 0\n first name: %s\n last name : %s\n age : %hhu\n", 
            people[0].first_name, people[0].last_name, people[0].age);

    batman.first_name = strdup ("Bat");
    batman.last_name = strdup ("Man");
    batman.age = 42;
    add_person (&people, batman, &population, &maxp);
    free_person_names (batman);

    printf("\nPerson 1\n first name: %s\n last name : %s\n age : %hhu\n", 
            people[1].first_name, people[1].last_name, people[1].age);

    for (i = 0; i < population; i++)
        free_person_names (people[i]);
    free (people);

    return 0;
}

/* add a person to an array of pointers to Person */
Person *add_person (Person **ppl, Person p, size_t *pop, size_t *max)
{
    if (*pop == *max)
        *ppl = realloc_person (*ppl, max);

    size_t i = (*pop)++;
    (*ppl)[i].first_name = strdup (p.first_name);
    (*ppl)[i].last_name = strdup (p.last_name);
    (*ppl)[i].age = p.age;

    return ppl[i];
}

/* realloc an array Person setting memory to 0. */
Person *realloc_person (Person *ppl, size_t *n)
{
    Person *tmp = realloc (ppl, 2 * *n * sizeof *ppl);
    if (!tmp) {
        fprintf (stderr, "Error: struct reallocation failure.\n");
        // return NULL;
        exit (EXIT_FAILURE);
    }
    ppl = tmp;
    memset (ppl + *n, 0, *n * sizeof *ppl); /* memset new ptrs 0 */
    *n *= 2;

    return ppl;
}

/* free only names of Person (for temp structs) */
void free_person_names (Person p)
{
    if (p.first_name) free (p.first_name);
    if (p.last_name) free (p.last_name);
}

Output

$ ./bin/struct_add_person

Person 0
 first name: Timn
 last name : Timothy
 age : 38

Person 1
 first name: Bat
 last name : Man
 age : 42

Comments

0

One problem is the last argument of add_person() to be specific, the argument '(struct Person) p'. When 'timn' and 'batman' are passed into the add_person() function, they are passed as a copy of the original structure. In the add_person() structure, that data is actually on the stack and is volatile outside the scope of the function. Try changing the last argument to a pointer.

3 Comments

What? It's a local variable yes but a copy is stored with (*people)[*population] = p; and that copy is not volatile. It is stored (As a valid variable). Like passing an int parameter to a function. If you store a copy in an array, it's not 'volatile'
My bad, but I was focusing more on the issue of copying a structure directly onto the stack prior to calling the function, which I try to avoid as I see it as bad programming practice, I did not look closely at how the stack is copied again into the allocated memory. The code is a bit over-complicated in how it was written.
You have a point. But i'm guessing that correct stack management and optimizations are probably not his primary concern. :-) And it definitely was written in an... interesting way. Those ifs are killing me.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.