2

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
10
  • 1
    Book *array[50]; creates an array of pointers to book 50 not 50 struct book. Commented Sep 20, 2016 at 18:46
  • 2
    typedef struct stack * Stack; is a misleading typedef. Use typedef struct stack Stack; or typedef struct stack * StackPtr; Commented Sep 20, 2016 at 18:48
  • @David Can you please elaborate on that? Commented Sep 20, 2016 at 18:49
  • 1
    Sure, If you want to create an array of 50 struct book, use Book array[50];, then you will access something like s->book[i].title. If you create an array of uninitialized pointers to struct book (e.g. Book *array[50];), you must allocate memory for each pointer before you can assign values to the members, etc. (you can do it this way, you just have to manage the memory allocation for each Book) Commented Sep 20, 2016 at 18:51
  • 4
    Never typedef a pointer! Commented Sep 20, 2016 at 18:52

3 Answers 3

2

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.

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

Comments

2

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.

Comments

0

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).

2 Comments

I did that but I get this error incomplete definition of type 'struct book' at this line: (s->array[s->num_books - 1])->title = strdup(title);
I don't understand. Could you edit the original post to add your new code without removing the original code?

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.