1

I'm writing an API that has structs such as

struct datast{
int a;
int *items;
size_t numitems;
};

I'm providing functions which free the contents of such structs (in a similar fashion to what C++ destructors do). Constructors are not provided because I'm mandating zero initialization for them (the .items field is required to be a NULL pointer on initialization, which makes it suitable for later realloc() and free()).

I'm providing however, an additem() function, which realloc()s .items and increases .numitems accordingly.

However, because these structs are small, I'd like to encourage the use of designated initializers and compound literals, so that users can conveniently create these objects with one-liners when possible, without having to manually invoke additem().

But then, if you initialize structs like these with designated initializers (or assign to them from a compound literal), the .items field will have automatic storage rather than allocated storage. And so, if you pass this struct to the "freeing" function/destructor later, you'll be calling free() with an illegal pointer (pointing to automatic storage).

Yes, I know the wording could be "don't call the destructor for objects for which you didn't call additem()"... but this looks really clumsy, and seems like bad design.

Somehow, it's like I had to decide if all these objects should have either automatic or allocated storage, without giving both possibilities to the user.

Have you ever been in a scenario like this? Is there any kind of design I could use that could provide a clean and elegant interface for both automatic and allocated storage?

cesss
  • 852
  • 1
  • 6
  • 15
  • So you want to identify on an arbitrary object if it's on the heap or not, and to call the free function for everything that could be a heap item? Recommendation: Never do this. 1) This is compiler and run-time lib dependent 2) If you have an stack or data segment object in the list, how will you make sure that the pointers in the object are still pointing to memory that is not `free`'ed?! Use your energy to get a better design. – harper Feb 20 '21 at 17:41
  • @harper: Precisely, that's what I don't want to do, and for sure I won't do that. What I'm asking for is the best design for this. But it's like I'm forced to give up on supporting one-liner initializers for this API, and that's sad. I'm asking for some design that could accommodate both kinds of storage. – cesss Feb 20 '21 at 17:45
  • 1
    It's like always: It's up to the user to track. The additem() function needs a flag telling it whether to free() the original data or not. You could potentially (if it's only about items and not about the struct as such) strore a flag *within the structure* which is set by your "factory" (or additem()) if the items need freeing. – Peter - Reinstate Monica Feb 20 '21 at 17:47

1 Answers1

1

Add a boolean member items_allocated. The zero initialisation that you mandate will make that false. Then additem() will set it true:

struct datast
{
    int a;
    int *items;
    bool items_allocated ;
    size_t numitems;
} ;

Then your destructor can have something like:

if( d->items_allocated )
{
    free( d->items ) ;
    d->items = NULL ;
}
d->numitems = 0 ;
...
Clifford
  • 88,407
  • 13
  • 85
  • 165
  • I think your solution is a good one, and most likely I'll mark it as accepted answer. Also, in the mean time, I realized about a possible situation I didn't think of: what if the user calls `additem()` for a struct that had the array initialized as automatic storage? Again, in that case, `additem()` should return error if `items_allocated` is false and `items` is not `NULL`. – cesss Feb 20 '21 at 18:58
  • Anyway, I feel like designated initializers and compound literals are sweet to use but create a sort of "dramatic tension" if you happen to have structs with array members, because you find yourself in the middle of an automatic vs allocated war. – cesss Feb 20 '21 at 19:03