2

I have the following code and I haven't worked in C for years and I'm confused by the syntax. How could I initialize two patrons within the init function? I assume I need to do some sizeof/malloc magic but really I'm confused on the syntax.

I believe I can access patron data like this:

t->seating[0].first_name 

However, I cannot get the dang array of seating created. Yes, my init function is empty as I've deleted all the crazy code I tried.

#include <stdio.h>

struct patron {
   char last_name[30];
   char first_name[30];
};

struct theatre_seating {
    struct patron **seating;
};

void init(int elem, struct theatre_seating *t);


int main() {
    struct theatre_seating theatre_seating;

    int elem = 2;


    init(elem, &theatre_seating);

    return 1;
}

void init(int elem, struct theatre_seating *t) {

}
KaiserKatze
  • 1,521
  • 2
  • 20
  • 30
DanielN
  • 21
  • 1
  • There is one too many star on this line: struct patron **seating; Your theatre wants to contain an 'array' of patrons, so just a single pointer is sufficient. You would need the two *s to have a 2D array of seating, or other use cases. – Evan Benn Sep 04 '18 at 05:00
  • BTW, this is just a tiny snippet of code for a rather large homework assignment. I inherited a **pointer and have to deal with it. It's the ** within the nested struct that I'm having trouble unwrapping. **seating will ultimately be a pointer to a 2-dimensional array holding patrons. I'll be getting the rows and cols at runtime and passing it to init(row, col, theatre_seating *t) and dealing with **seating in the real function. All that aside, I have to implement **seating not *seating – DanielN Sep 05 '18 at 05:05
  • Then you need to change `t->seating[0].first_name` to `t->seating[0][0].first_name` . Two *s in the declaration match 2 stars (or []) in the usage. – Evan Benn Sep 05 '18 at 05:56

2 Answers2

2
#include <stdio.h>
// First you need to include necessary header files,
// to invoke memory allocation functions,
// such as `malloc`, `calloc`, etc.
#include <stdlib.h>

struct patron {
    // Beware the following declaration leads to inefficient memory usage
    char last_name[30];
    char first_name[30];
};

struct theatre_seating {
    // Keeps a track of how long the following array is
    size_t len_seating;
    // Array pointer
    struct patron * seating;
};

int init(int elem, struct theatre_seating *t);

int main() {
    // Usually people initialize a `struct` with `{ 0 }`
    // to make sure every member in it is empty,
    // and to prevent undefined behaviour
    struct theatre_seating theatre_seating = { 0 };

    int elem = 2;

    if (init(elem, &theatre_seating))
    {
        // TODO
    }

    // ALWAYS remember to destroy
    // dynamically allocated memory
    // at the end of the thread
    free(theatre_seating.seating);

    // Instead of (1), return (0) to indicate successful result.
    return 0;
}

// I don't know what argument `elem` stands for
// but I assume that it stands for the number of seats
// (aka. the length of `seating` array)
//
// @return: 1, upon successful memory allocation
//          0, otherwise
int init(int elem, struct theatre_seating *t) {
    t->len_seating = elem;
    t->seating = calloc(elem, sizeof(struct patron));

    // Remember to check if dynamic allocation is successful
    return (int) t->seating;
}

Update:

int create_seating(int nRows, int nCols, struct patron ** seating)
{
    int i = 0;

    seating = (struct patron **) calloc(nRows, sizeof(struct patron *));
    if (seating)
    {
        for (; i < nRows; i++)
        {
            seating[i] = (struct patron *) calloc(nCols, sizeof(struct patron));
            if (seating[i])
                continue;
            return 0;
        }
    }

    return i;
}

void destroy_seating(int nRows, struct patron ** seating)
{
    int i;

    if (seating)
    {
        for (i = 0; i < nRows; i++)
        {
            free(seating[i]);
        }

        free(seating);
    }
}
KaiserKatze
  • 1,521
  • 2
  • 20
  • 30
  • 1
    `free(NULL)` is a no-op; you don't need to check the pointer beforehand. – melpomene Sep 04 '18 at 05:33
  • 1
    I recommend avoiding identifiers starting with `_`. A single leading `_` followed by a lowercase letter is technically fine on local variables, but why tempt fate? – melpomene Sep 04 '18 at 05:34
  • 1
    You don't need that `memset` call. Leaving the memory uninitialized doesn't cause undefined behavior. If you do need to set it to 0, `calloc` instead of `malloc` / `memset` would be more efficient. As a side effect, it also prevents integer overflow in `elem * sizeof(struct patron)`. – melpomene Sep 04 '18 at 05:36
  • Now that you've edited your code, `memset` is gone, so you don't need `#include ` anymore. Technically you don't need the return value from `init` either because the caller can just check `if (theatre_seating.seating)` directly. :-) – melpomene Sep 04 '18 at 05:54
  • 1
    I'd like to keep memory allocation check inside `init`, bcoz in my mind it should be part of its logic. :P – KaiserKatze Sep 04 '18 at 05:57
  • KaiserKatze - I have to retain the double pointer **seating. This is a small snippet of code for a quite large homework project. My issue is with **seating within the nested structs. NominalAnimal's reply below appears to clarify how to create/access the array elements while retaining **seating which is to be a pointer to an array of struct patrons. The portion of the real program I have to write which is very loosely represented in this post will be getting the row/col at runtime for which I have to create a two-dimensional array of patrons of that size which **seating will point to. – DanielN Sep 05 '18 at 05:39
  • It's unnecessary to implement a 2-dimensional array to access rows and columns of a matrix. Check out [this post](https://stackoverflow.com/q/51962613/4927212). – KaiserKatze Sep 05 '18 at 05:58
  • Anyway if you must retain the double pointer `**seating`, then yes, you need to allocate memory for the 2-dimensional array, and check out my update. – KaiserKatze Sep 05 '18 at 06:00
2

Here's an alternative approach, that should be a bit more robust. It uses C99's flexible array member.

Instead of fixed-size arrays, put the character data to the flexible array member. The first name is stored first, followed by (an end of string NUL, \0 and) the last name (and an another end of string NUL).

To avoid having to find where the first name starts, we can store either a pointer, or an offset. I prefer the offset, but as long as you (the programmer!) are careful, the pointer will work fine as well:

struct patron {
    char *last_name;    /* Points to within the first_name member */
    char  first_name[]; /* Flexible array member */
};

Usually, you write helper functions to allocate and initialize, as well as free, such structures:

void free_patron(struct patron *p)
{
    if (p) {
        /* "Poisoning" the structure, to help detect possible use-after-free bugs. */
        p->last_name = NULL;
        p->first_name[0] = '\0';

        /* Both names reside in the same dynamically allocated part. */
        free(p);
    }
}

struct patron *new_patron(const char *first, const char *last)
{
    const size_t   firstlen = (first) ? strlen(first) : 0;
    const size_t   lastlen = (last) ? strlen(last) : 0;
    struct patron *newpatron;

    /* Don't allow unnamed patrons. */
    if (firstlen + lastlen < 1) {
        fprintf(stderr, "new_patron(): NULL or empty name.\n");
        exit(EXIT_FAILURE);
    }

    /* Allocate enough memory for the structure. */
    newpatron = malloc(sizeof (struct patron) + firstlen + 1 + lastlen + 1);
    if (!newpatron) {
        fprintf(stderr, "new_patron(): Not enough memory.\n");
        exit(EXIT_FAILURE);
    }

    /* First name goes first. */
    if (firstlen > 0)
        memcpy(newpatron->first_name, first, firstlen);
    newpatron->first_name[firstlen] = '\0';

    /* Last name follows. */
    newpatron->last_name = newpatron->first_name + firstlen + 1;
    if (lastlen > 0)
        memcpy(newpatron->last_name, last, lastlen);
    newpatron->last_name[lastlen] = '\0';

    return newpatron;
}

To manage an array of patrons, this time each entry is a pointer to a struct patron. This means you can choose whether you use a fixed-size array, where you locate a vacant seating by locating a NULL pointer.

struct seating {
    size_t           seats;
    struct patron  **seat;
};

#define  NO_VACANCIES  (~(size_t)0)

void free_seating(struct seating *s)
{
    if (s) {
        free(s->seat);
        s->seats = 0;
        s->seat = NULL;
    }
}

void init_seating(struct seating *s, const size_t n)
{
    size_t  i;

    if (!s) {
        fprintf(stderr, "init_seating(): NULL pointer to struct seating.\n");
        exit(EXIT_FAILURE);
    }

    /* No seats wanted at all? */
    if (n < 1) {
        s->seats = 0;
        s->seat  = NULL;
        return;
    }

    s->seat = malloc(n * sizeof s->seat[0]);
    if (!s->seat) {
        fprintf(stderr, "init_seating(): Not enough memory.\n");
        exit(EXIT_FAILURE);
    }
    s->seats = n;

    /* Initialize all seats as vacant. */
    for (i = 0; i < n; i++)
        s->seat[i] = NULL;

    /* Done. */
}

/* Find a vacant/unused seating.
   Returns the seat index, or NO_VACANCIES if all taken. */
size_t vacant_seating(struct seating *s)
{
    size_t  i;

    if (!s || s->seats < 1)
        return NO_VACANCIES;

    for (i = 0; i < s->seats; i++)
        if (!s->seat[i])
            return i; /* Seat i is vacant. */

    return NO_VACANCIES;
}

/* Removes a patron from a seating.
   You'll usually want to call
       free_patron(release_seating(&my_threatre, place));
   to free the structure naming the patron as well.
   This is safe to do even if the seat was vacant. */
struct patron *release_seating(struct seating *s, size_t i)
{
    if (s && i < s->seats) {
        struct patron *old_patron = s->seat[i];
        s->seat[i] = NULL;
        return old_patron;
    } else
        return NULL;
}

In your program, using these is simple:

struct seating  my_theatre;
size_t          place;

/* Small venue with 50 seats. */
init_seating(&my_theatre, 50);

/* Find a vacant seat. */
place = vacant_seating(&my_theatre);
if (place == NO_VACANCIES) {
    fprintf(stderr, "Sorry, the theatre is full.\n");
    return EXIT_FAILURE;
}

/* Seat DanielN there. */
my_theatre.seat[place] = new_patron("Daniel", "N");

Note that because my_theatre.seat is an array, my_theatre.seat + place is a pointer to the placeth element in the array, exactly like &(my_theatre.seat[place]).

Also note that when allocating arrays, say struct something *foo;, you can use the sizeof operator: foo = malloc(n * sizeof foo[0]); tries to allocate enough memory for n elements of whatever type foo[0] is. Note that to help us programmers remember that sizeof is an operator, and not a function. Even when foo is undefined or NULL, sizeof foo[0] is valid, because the sizeof operator only examines the type of its argument to determine the size of the type.

The NO_VACANCIES macro evaluates to the largest size_t value (that the type can describe in binary on non-binary computers). That expression works for all unsigned integer types, and size_t is an unsigned (nonnegative) integer type. It would be better to include <limits.h> and use the SIZE_MAX (similar to CHAR_MAX, UCHAR_MAX, INT_MAX, and so on that that header file defines), but I'm not sure if all (well, Microsoft; they like to do things their own way) define SIZE_MAX.

Nominal Animal
  • 38,216
  • 5
  • 59
  • 86
  • I was going to ultilize **Flexible array member**, but I didn't know its compiler support. – KaiserKatze Sep 04 '18 at 06:31
  • Could you please demonstrate C99 compiler support, please? – KaiserKatze Sep 04 '18 at 06:44
  • 1
    @KaiserKatze: All current C compilers should support flexible array members (and all of the code in my example). I suspect only Borland C and perhaps very old versions of Microsoft C (before 2013) might not support those; I definitely expect anything that claims to be a C compiler to support flexible array members. After all, 1999 was 19 years ago, so assuming C99 or later support is reasonable. – Nominal Animal Sep 04 '18 at 07:03
  • My issue was with the double pointer **seating and how to create/access the seating elements. Nominal Animal clarified the syntax in the "Note that because my_theatre.seat is an array..." This being a homework thing, I actually have to create helper functions based upon provided function prototypes so I will not be stealing your code. I thought I'd get a simple response of here's how'd you'd create a couple patrons inside the init() function and I'd extrapolate the rest into proper code. There's obviously a few different ways to tackle this given the responses, but **seating is required. – DanielN Sep 05 '18 at 05:51