user6005857
user6005857

Reputation: 631

How can I access an array of structs within a struct?

I have the following two structures:

typedef struct stack * StackPtr;
typedef struct book * BookPtr;

struct book {
    char *title;
    int pages;
};

struct stack {
    int num_books;
    Book array[50] // changed from Book *array[50]
};

Here is how I'm initialising each structure:

StackPtr create_stack(void) {
    StackPtr s = malloc(sizeof(struct stack));
    s->num_books = 0;
    return s;
}

BookPtr create_book(char *title, int pages) {
    BookPtr b = malloc(sizeof(struct book));
    b->title = strdup(title);
    b->pages = pages;
    return b;
}

I am trying to access the title and the pages of the book using the array inside the stack struct. This is how I am doing it:

For example, if I want to access and modify the title and pages of the last book in the struct, I am dong this:

(s->array[s->num_books-1])->title = strdup(new_title);
(s->array[s->num_books-1])->pages = new_pages;

However, I am getting this error:

error: member reference base type 'Book' (aka 'struct book *') is not a structure or union
        (s->array[s->num_books-1])->title = strdup(new_title);
        ~~~~~~~~~~~~~~~~~~~~~~~~~~^ ~~~~~

Edit: after I removed the pointer from Book *array[50]; I am now getting this error:

error: incomplete definition of type 'struct book'
        (s->array[s->num_books-1])->title = strdup(new_title);
        ~~~~~~~~~~~~~~~~~~~~~~~~~~^

I thought this was a problem with main not being able to see the book structure so I stopped using header files and put everything in one file but that didn't help.

I also think that I am not initialising the stack properly.

When I try to initialize the array in the create_stack function like this:

s->array = malloc(sizeof(BookPtr) * 50); //inside create_array function

This is the error I get:

array type 'BookPtr *[50]' is not assignable

Upvotes: 2

Views: 691

Answers (3)

David C. Rankin
David C. Rankin

Reputation: 84521

It is unclear whether you ever got this solved, so perhaps a short example will help. Regardless whether you create storage for X number of books in array at the beginning or create array as an an array of X pointers to books, you will need some way of insuring you do not add more books than you can store. The obvious way to handle this is to add one additional variable to your stack struct that tracks the storage (or number of pointers available) in array and then realloc array as required. To track the space in array available, you could simply add another counter, say max_books, e.g.

enum { NBOOKS = 10 };

typedef struct {
    char *title;
    int pages;
} book;

typedef struct {
    int num_books,
        max_books;
    book *array;
} stack;

Since there is no benefit in declaring an array as an array of pointers when you are simply going to create storage for each book, you may as well just declare array as book *array; and allocate storage for some reasonably anticipated number of books to begin with. Your create_stack isn't far off, but I would do add_book a little differently, in order to create the stack if it is currently NULL and realloc as required. Something like the following:

/** since add_book may create the stack, you must pass the address
 *  of the stack to add_book so that any changes to s are available
 *  back in the calling funciton.
 */
book *add_book (stack **s, char *title, int pages)
{
    if (!title) return NULL;        /* validate title */
    if (!*s) *s = create_stack ();  /* if stack NULL, create */

    /* check num_books against max_books and realloc as required */
    if ((*s)->num_books == (*s)->max_books) {
        void *tmp = realloc ((*s)->array, ((*s)->max_books + NBOOKS) * 
                            sizeof *((*s)->array));
        if (!tmp) {
            fprintf (stderr, "error: memory exhausted - realloc array.\n");
            return NULL;
        }
        (*s)->array = tmp;
        (*s)->max_books += NBOOKS;
    }

    /* allocate/copy title, assign pages, increment num_books */
    (*s)->array[(*s)->num_books].title = strdup (title);
    (*s)->array[(*s)->num_books].pages = pages;
    ((*s)->num_books)++;

    /* change return as desired, I just return the address of the book
     * to indicate success and provide a way to validate the add.
     */
    return &((*s)->array[(*s)->num_books - 1]);
}

(note: the comment on why the stack is passed to the function as stack **)

Those are basically the changes you would need to create your stack of books that would allow you to add as many books as you would like (until you exhaust the memory of your computer). Putting the example together, you can do something like the following (note: the constant NBOOKS must be greater than zero, you can add checks to insure)

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

enum { NBOOKS = 10 };

typedef struct {
    char *title;
    int pages;
} book;

typedef struct {
    int num_books,
        max_books;
    book *array;
} stack;

stack *create_stack ();
book *add_book (stack **s, char *title, int pages);
void prn_stack (stack *s);
void free_stack (stack *s);

int main (void) {

    stack *s1 = NULL;   /* always initialize your pointers */

    add_book (&s1, "Huck Finn", 631);
    add_book (&s1, "Tom Sawyer", 582);
    add_book (&s1, "The Quick Brown Fox", 1);

    prn_stack (s1);
    free_stack (s1);

    return 0;
}

/** allocate stack and allocate storage for NBOOKS books */
stack *create_stack ()
{
    stack *s = calloc (1, sizeof *s);
    if (!s) {
        fprintf (stderr, "error: virtual memory exhausted - stack.\n");
        exit (EXIT_FAILURE);
    }

    s->array = calloc (NBOOKS, sizeof *(s->array));
    if (!s->array) {
        fprintf (stderr, "error: virtual memory exhausted - array.\n");
        exit (EXIT_FAILURE);
    }

    s->num_books = 0;
    s->max_books = NBOOKS;

    return s;
}

/** since add_book may create the stack, you must pass the address
 *  of the stack to add_book so that any changes to s are available
 *  back in the calling funciton.
 */
book *add_book (stack **s, char *title, int pages)
{
    if (!title) return NULL;        /* validate title */
    if (!*s) *s = create_stack ();  /* if stack NULL, create */

    /* check num_books against max_books and realloc as required */
    if ((*s)->num_books == (*s)->max_books) {
        void *tmp = realloc ((*s)->array, ((*s)->max_books + NBOOKS) * 
                            sizeof *((*s)->array));
        if (!tmp) {
            fprintf (stderr, "error: memory exhausted - realloc array.\n");
            return NULL;
        }
        (*s)->array = tmp;
        (*s)->max_books += NBOOKS;
    }

    /* allocate/copy title, assign pages, increment num_books */
    (*s)->array[(*s)->num_books].title = strdup (title);
    (*s)->array[(*s)->num_books].pages = pages;
    ((*s)->num_books)++;

    /* change return as desired, I just return the address of the book
     * to indicate success and provide a way to validate the add.
     */
    return &((*s)->array[(*s)->num_books - 1]);
}

void prn_stack (stack *s)
{
    if (!s) return;

    printf ("\nThere are %d books in the stack:\n\n", s->num_books);
    for (int i = 0; i < s->num_books; i++)
        printf ("  %2d.  %-20s  (%3d pages)\n", i, s->array[i].title, s->array[i].pages);
    putchar ('\n');
}

void free_stack (stack *s)
{
    if (!s) return;

    for (int i = 0; i < s->num_books; i++)
        free (s->array[i].title);
    free (s->array);
    free (s);
}

Example Use/Output

$ ./bin/bookstack

There are 3 books in the stack:

   0.  Huck Finn             (631 pages)
   1.  Tom Sawyer            (582 pages)
   2.  The Quick Brown Fox   (  1 pages)

Memory Use/Error Check

Setting NBOOKS TO 2 to force reallocation and checking with valgrind, you will find:

$ valgrind ./bin/bookstack
==15521== Memcheck, a memory error detector
==15521== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==15521== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==15521== Command: ./bin/bookstack
==15521==

There are 3 books in the stack:

   0.  Huck Finn             (631 pages)
   1.  Tom Sawyer            (582 pages)
   2.  The Quick Brown Fox   (  1 pages)

==15521==
==15521== HEAP SUMMARY:
==15521==     in use at exit: 0 bytes in 0 blocks
==15521==   total heap usage: 6 allocs, 6 frees, 153 bytes allocated
==15521==
==15521== All heap blocks were freed -- no leaks are possible
==15521==
==15521== For counts of detected and suppressed errors, rerun with: -v
==15521== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 1 from 1)

Which is how it should be. Look things over and let me know if you have any questions.

Upvotes: 2

Sami Kuhmonen
Sami Kuhmonen

Reputation: 31143

You have defined Book as a pointer to struct book and then you are defining an array of pointers of type Book, which means it's an array of pointers to pointers to struct book.

Either define Book as struct book or remove the pointer from after Book in the array definition. Usually the former since you didn't name it clearly as BookPtr for example.

Upvotes: 2

Loring
Loring

Reputation: 322

I can't comment, so I will expound on David C. Rankin's comment. Your definition of array won't work.

You probably should use one of the following...

This gives you an array of Book that will hold at most 50 books:

Book array[50];

This gives you a pointer to an array of Book that has no size limit, but requires more overhead.

Book *array;

If you use the latter, you will have to create the array, keep track of its current length, and make it bigger when you've filled it up (see https://linux.die.net/man/3/realloc).

Upvotes: 0

Related Questions