Define a macro that takes a struct instance and calls "free" for each "void *" member

I have several similar types of structure.

Each type contains several members of different types.

For example:

struct A
{
    char  a1;
    short a2;
    void* a3;
    int   a4;
    void* a5;
};

struct B
{
    void* b1;
    long  b2;
    void* b3;
    void* b4;
};

      

I want to define a macro that takes an instance of a struct and calls free

for each member void*

.

I guess it __VA_ARGS__

might be helpful here, but I'm not really sure how.

Any alternative ideas (including runtime solutions) will also be appreciated.

+3


source to share


6 answers


Maybe with X Macros?



#define structAFields \
    X(char, a1, ) \
    X(short, a2, ) \
    X(void*, a3, ) \
    X(int, a4, ) \
    X(void*, a5, ) \

#define X(A, B, C) A B C;
struct A
{
    structAFields
};
#undef X

#define X(A, B, C) if(strcmp(#A, "void*") == 0) *((void **)((void *)(&a->B))) = malloc(2);
void mallocA(struct A * a)
{
    structAFields
}
#undef X

#define X(A, B, C) if(strcmp(#A, "void*") == 0) free(*((void **)((void *)(&a->B))));
void freeA(struct A * a)
{
    structAFields
}
#undef X

int main(int argc, char **argv) {
    struct A a;

    mallocA(&a);
    freeA(&a);
}

      

+4


source


I don't think this is possible, C has no introspection at this level.

The classic approach is to add runtime type information to structures eg. there is

typedef enum {
  StructType_A,
  StructType_B
} StructType;

      

then add one of these to the beginning of each structure:



struct A {
  StructType type;
  ...
};

      

Now you can write a function that checks to what type of structure it received a pointer to, and whether it performs the appropriate allocation:

void free_struct(void *s)
{
  const StructType type = *(StructType *) s;

  switch(type)
  {
  case StructType_A:
    {
      struct A *ap = s;
      free(ap->a3);
      free(ap->a5);
    }
    break;
  case StructType_B:
    {
      ...
    }
    break;
  }
}

      

Of course, you could make the actual calls free()

differently, for example having a list of field offsets for each of the supported types, rather than hardcoding.

+3


source


X macros can implement a single definition, different behavior mechanisms, and Mabus showed an example. I would like to extend this idea by using two X macros (or one XY macro, rather), one for unmanaged scalar notations and one for pointers.

The structure definitions will look like this:

#define STRUCT_A(SCAL, PTR) \
    SCAL(int i)             \
    SCAL(double x)          \
    PTR(p1)                 \
    PTR(p2)

#define STRUCT_B(SCAL, PTR) \
    PTR(p)                  \
    PTR(q)                  \
    PTR(r)

#define STRUCT_C(SCAL, PTR) \
    SCAL(int x)             \
    SCAL(int y)             \
    PTR(r)

      

This is probably ugly, but I don't see any other way. There are two internal macros SCAL

and PTR

, and they are passed as arguments instead of #defined

and #undef

, entered before and after calling the main macro.

Now we need macros that implement definition and cleanup for structures:

#define SCAL_DEF(X) X;
#define PTR_DEF(X) void *X;

#define SCAL_FREE(X) 
#define PTR_FREE(X) free(TMP__STRUCT->X);

#define STRUCT_DEF(A) struct A { STRUCT_##A(SCAL_DEF, PTR_DEF) }

#define STRUCT_FREE(A, a) do {          \
    struct A *TMP__STRUCT = a;          \
    STRUCT_##A(SCAL_FREE, PTR_FREE)     \
} while (0)

      

In the client code, only the last two are used, STRUCT_DEF

and STRUCT_FREE

, and they are used as follows:

STRUCT_DEF(A);

void a_init(struct A *a)
{
    memset(a, 0, sizeof(*a));
    // initialise a
}

void a_do_stuff(struct A *a)
{
    // do stuff with a
}

void a_free(struct A *a)
{
    // clean-up a other than freeing void *
    STRUCT_FREE(A, a);
}

      

These macros use token binding and rely on those struct Thingy

defined by the X macro STRUCT_Thingy

.

I think it's not too bad if you put the X macro just before STRUCT_DEF

, but:

  • Unmanaged and managed types are distinguished by two different macros, which means that the user must make a distinction when writing the macro, so it is not "automatic".
  • The user must provide the name of the macro as arguments in the main X macro, which could be the source of the error.
  • The cleanup mask relies on a temporary variable with an unlikely name that is not very clean coding.
+1


source


Why not use an array void *

?

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

#define free_voids(obj) free_voids(obj, sizeof(obj) / sizeof(obj[0]))

struct A
{
    char    a1;
    short   a2;
    #define a3 voids[0]
    int     a4;
    #define a5 voids[1]
    void      *voids[2];
};

struct B
{
    #define b1 voids[0]
    long    b2;
    #define b3 voids[1]
    #define b4 voids[2]
    void      *voids[3];
};

void (free_voids)(void **obj, size_t elems)
{
    size_t i;

    for (i = 0; i < elems; i++) {
        free(obj[i]);
    }
}

int main(void) 
{
    struct A *a = malloc(sizeof *a);
    struct B *b = malloc(sizeof *b);

    a->a1 = 1;
    a->a2 = 1;
    a->a3 = malloc(1);
    a->a4 = 1;
    a->a5 = malloc(1);
    free_voids(a->voids);
    free(a);

    b->b1 = malloc(1);
    b->b2 = 1;
    b->b3 = malloc(1);
    b->b4 = malloc(1);
    free_voids(b->voids);
    free(b);

    return 0;
}

      

+1


source


Here's a different approach: Define a managed scope in a struct that contains all the void pointers you want to handle automatically:

struct A {
    int x;
    int y;

    MANAGED(p, q, r);
};

      

This structure having two integers, and three controlled void pointer p

, q

and r

behaves as:

struct A {
    int x;
    int y;

    void *p;
    void *q;
    void *r;
};

      

There can be only one managed void pointer area per structure. When cleaning up, just call:

void a_free(struct A *a)
{
    FREE_MANAGED(a);   // free(a->p); free(a->q); free(a->r);
}

      

This minimal interface can be implemented using these macros:

typedef void *managed_t;

#define MANAGED(...)                            \
    managed_t managed_begin_;                   \
    managed_t __VA_ARGS__;                      \
    managed_t managed_end_

#define FREE_MANAGED(S) do {                    \
        managed_t *p_ = &(S)->managed_begin_;   \
        managed_t *end_ = &(S)->managed_end_;   \
        while (++p_ < end_) free(*p_);          \
    } while (0)

      

There is a need for a special type managed_t

, since it allows you to expand a macro in a single-line definition of many pointers of the same type using an argument __VA_ARGS__

taken as an integer. Pointers cannot be declared by this method because it will give something like 'void * a, b, c , ofg which only the first is a pointer. Typedefs in C are aliases to types, so no harm is done and

managed_t is equivalent to

void *' and effectively hide from the user.

The ease of use of macros comes from two additional accounts that are also managed_t

. (This can be wasteful, but it simplifies the implementation. I can imagine that the managed approach is only useful for structures with a lot of records, however, that two additional pointers are not too heavy.)

This solution is for macros only (except typedef

) and can be placed in a recovered file.

+1


source


Higher order macros are a similar alternative to X macros. They are a little harder to get used to, but closer to normal functional programming, and tend to include much lighter syntax in the end result code.

Using M_FOR_EACH

etc.
(related to limiting clutter in this answer), you can define macros that iterate over a lightweight list of members to create a struct definition and any kind of processing:

#define DECLARE(N) DECLARE_(N, M_ID STRUCT_##N)   // not CONC
#define DECLARE_(N, ...) \
    struct N { M_FOR_EACH(SEMIC, __VA_ARGS__) }; \
    void M_CONC(free_, N) (struct N * s) { M_FOR_EACH(FREE_FIELD, __VA_ARGS__) }

#define SEMIC(D) D;
#define FREE_FIELD(F) M_CONC(FREEVOIDP_, M_NARGS(M_CONC(TRYFREE_, F)))(M_CONC(DOFREE_, F))
#define TRYFREE_voidp %,%
#define DOFREE_voidp
#define FREEVOIDP_1(F)
#define FREEVOIDP_2(F) free(s->F);

typedef void * voidp;


#define STRUCT_A (char  a1, \
                  short a2, \
                  voidp a3, \
                  int   a4, \
                  voidp a5)
DECLARE(A)

#define STRUCT_B (voidp b1, \
                  long  b2, \
                  voidp b3, \
                  voidp b4)
DECLARE(B)

      

Define your structure as a simple member list (this is much easier on the eye than X macros in user code), with whatever you want from a free operation declared as a type voidp

. (It won't work with a regular one void*

, but I feel like the typedef provides a bit more documentation about something special going on with these fields, so it's acceptable.) The definition name must start with STRUCT_

; this allows the declaring macro to later separate the name from the body.

The first loop just inserts semicolons so that the list can be inserted inside the structure declaration.

The second loop can identify the members voidp

(by adding TRYFREE_

to the type and seeing which ones expand to sequences with multiple tokens) and only emits a free function for them. This is where the need for a typedef comes in: cannot be decoupled *

from the principal name using a preprocessor. (An alternative is a comma to separate types from element names in the original definition, which is difficult to read.)

+1


source