3
#include <stdio.h>
#include <limits.h>

typedef struct item {
    int low; int high; char label[16];
} Item;

typedef struct item_coll {
    size_t length; Item *items[];
} ItemColl;

char *find_first_in_range(ItemColl *ic, int rlow, int rhigh) {
    for (size_t i = 0; i < ic->length; i++)
        if (ic->items[i]->low >= rlow && ic->items[i]->high <= rhigh)
            return &ic->items[i]->label[0];
    return NULL;
}

int main() {
    struct item fruits[] = {
        {10, 20, "Apple"},
        {12, 14, "Pear"},
        {8, 12, "Banana"},
        {2, 4, "Grape"},
        {15, 35, "Watermelon"}
    };

    struct item_coll basket = {5, fruits};

    printf("%s", find_first_in_range(&basket, 21, 22));

    return 0;
}

This is giving me app.c:28:32: error: non-static initialization of a flexible array member struct item_coll basket = {5, fruits};

and the error is pointing to fruits.

What does this mean? It looks good to me.

Vegas
  • 8,017
  • 1
  • 10
  • 14
  • 1
    The standard says you can’t do that. GCC might have other views on the topic, but it isn’t portable. (In particular, if the array was `static` or at file scope, you might get away with it with GCC.) You can’t initialize VLA or FAM in a standard-conforming program. – Jonathan Leffler Dec 29 '18 at 13:48
  • @JonathanLeffler I've done this before and it worked fine, what do you mean 'VLA' and 'FAM'? – Vegas Dec 29 '18 at 13:50
  • VLA = variable length array; FAM = flexible array member. – Jonathan Leffler Dec 29 '18 at 13:51
  • 2
    As I said (in an edited version of my opening comment), the standard says one thing and GCC does things it’s own way. The mention of “non-static” in the error message is conveying important information. – Jonathan Leffler Dec 29 '18 at 13:53
  • `size_t length; Item *items[];`, where `items` is a FAM and it is more a placeholder which allows you to allocate storage for the struct + X number of `Item *` pointers in a single allocation. By itself `Item *items[];` is an *Incomplete type* because there is no specification of how many pointers there are. – David C. Rankin Dec 29 '18 at 13:55

2 Answers2

7

If this is an exam, and you must use a Flexible Array Member, then as I indicated in the comments and as @rici explained in his answer, the purpose of the FAM is to provide a placeholder of a given type that then allows you to allocate storage for the struct itself plus storage for some number of your FAM type in a single-allocation. The advantage this provides is a single-allocation/single-free rather than separate allocation for the struct and then allocating for some number of your needed type.

(prior to the FAM, there was what was referred to as the struct hack where an array of size 1 was used in its place for much the same purpose)

The type is critical to how you deal with and allocate for your FAM. In your case your FAM is item *items[]; (an array of pointers to type item -- Item in your code) So you allocate for the struct and then X number of pointers to item.

To initialize each member of items, you must assign a valid address to a struct of type item (or you can separately allocate, copy to the new block, and then assign the starting address for that block to a pointer in items) In your case, you have an array of struct item called fruits. To assign to items, you must assign the address of each struct to each element in items (remember you have storage for pointers, not storage for struct item -- and you must ensure fruits remains in scope for the duration of your use of basket)

Putting those pieces together, you could do something similar to the following:

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

typedef struct {
    int low, high;
    char label[16];
} item;

typedef struct {
    size_t length;
    item *items[];
} item_coll;

char *find_first_in_range(item_coll *ic, int rlow, int rhigh) 
{
    for (size_t i = 0; i < ic->length; i++)
        if (ic->items[i]->low >= rlow && ic->items[i]->high <= rhigh)
            return ic->items[i]->label;
    return NULL;
}

int main() {

    item fruits[] = {
        {10, 20, "Apple"},
        {12, 14, "Pear"},
        { 8, 12, "Banana"},
        { 2,  4, "Grape"},
        {15, 35, "Watermelon"}
    };
    size_t nfruits = sizeof fruits/sizeof *fruits;  /* avoid magic-numbers */
    /* allocate storage for basket + nfruits pointers */
    item_coll *basket = malloc (sizeof *basket + 
                                nfruits * sizeof *basket->items);
    if (!basket) {  /* validate allocation succeeded */
        perror ("malloc-basket+5_item_coll");
        return 1;
    }
    basket->length = nfruits;   /* assign length */

    for (size_t i = 0; i < nfruits; i++)  /* assign addresses to structs */
        basket->items[i] = &fruits[i];

    char *label = find_first_in_range (basket, 12, 15);  /* save return */
    if (label)  /* validate not NULL before printing */
        printf ("%s\n", label);

    free (basket);  /* don't forget to free the memory you allocate */

    return 0;
}

(note I have simply used typedefs and removed the struct labels themselves -- it's up to you. Further, you should validate the return from find_first_in_range is not NULL before printing.)

Example Use/Output

Also note I've bracket the high/low range for

$ ./bin/fam_initialization
Pear

Memory Use/Error Check

In any code you write that dynamically allocates memory, you have 2 responsibilities regarding any block of memory allocated: (1) always preserve a pointer to the starting address for the block of memory so, (2) it can be freed when it is no longer needed.

It is imperative that you use a memory error checking program to insure you do not attempt to access memory or write beyond/outside the bounds of your allocated block, attempt to read or base a conditional jump on an uninitialized value, and finally, to confirm that you free all the memory you have allocated.

For Linux valgrind is the normal choice. There are similar memory checkers for every platform. They are all simple to use, just run your program through it.

$ valgrind ./bin/fam_initialization
==6887== Memcheck, a memory error detector
==6887== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==6887== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==6887== Command: ./bin/fam_initialization
==6887==
Pear
==6887==
==6887== HEAP SUMMARY:
==6887==     in use at exit: 0 bytes in 0 blocks
==6887==   total heap usage: 1 allocs, 1 frees, 48 bytes allocated
==6887==
==6887== All heap blocks were freed -- no leaks are possible
==6887==
==6887== For counts of detected and suppressed errors, rerun with: -v
==6887== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Always confirm that you have freed all memory you have allocated and that there are no memory errors.

Look things over and let me know if you have further questions.

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

I strongly suspect you intended this definition:

typedef struct item_coll {
    size_t length; Item *items;
} ItemColl;

i.e. a length and an array of Items, not an array of pointers to Item.

As written, the array of pointers does not have any specified length, which makes the struct effectively incomplete; the compiler cannot tell hiw big it is so you can never actually define one. C11 allows this sort of declaration for convenience. The array of unspecified length -- the "flexible array member" -- must come at the very end of the struct and the struct itself cannot be used inside another struct.

You can use a struct with a flexible array member as a kind of base for a concrete dynamically allocated object whose size can be computed as sizeof (Struct) + n * sizeof (ArrayElement). This is occasionally useful as an optimisation and it is legal; all I will say is that its apparent advantages need to be weighed against the code complications it creates.

rici
  • 234,347
  • 28
  • 237
  • 341
  • Thing is, this is a question from an exam and there they actually have `Item *items[]`. Could it be a mistake or do you think there might be a meaning behind it? – Vegas Dec 29 '18 at 14:05
  • @Vegas — what’s written with a FAM is valid, but you can’t initialize the structure with the FAM in standard C. What’s suggested here is an alternative way of doing things, but it isn’t a FAM. – Jonathan Leffler Dec 29 '18 at 14:13
  • @vegas: it's true that the function seems to expect its argument to have an array of pointers; otherwise it would use, for example, `ic->items[i].lo` instead of `->low`. But `fruits` is not an array of pointers. It is an array of `Items`. So if the code is intended as an intended use case, the function is wrong. If, on the other hand, it's intended to illustrate the use of flexible array members, initialization is impossible so it mostly serves to ilustrate the inconvenience. – rici Dec 29 '18 at 14:27
  • And even if a flexible array member were intended, it would make more sense to use `size_t length; Item items[];` -- a flexible array of Items -- than the flexible array of pointers to items. That would save the overhead of indirection, if that were a concern. If an array of pointers to Items were desired, it would probably prove simpler to NULL-terminate the array than to store an explicit length. – rici Dec 29 '18 at 14:37