Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compile time check polymorphic types in C?

Tags:

c

type-safety

c11

Polymorphic structs are quite common in C but often involve explicit casts which allow for accidentally casting incompatible structs.

struct ID {
    char name[32];
};

struct IntID {
    struct ID id_base;
    int value;
}
struct FloatID {
    struct ID id_base;
    float value;
}

void id_name_set(ID *id, const char *name)
{
    strlcpy(id->name, name, sizeof(id->name));
}

/* macro that happens to use 'id_name_set', this is a bit contrived */
#define ID_NAME_SET_AND_VALUE(id, name, val) \
    do { \
        id_name_set((ID *)id, name); \
        id->value = val; \
    } while(0)

void func(void)
{
    struct { int value; } not_an_id;

    /* this can crash because NotID doesn't have an ID as its first member */
    ID_NAME_SET_AND_VALUE(not_an_id, "name", 10);
}

The issue here is we can't type check the id argument in the macro against a single type, since it could be an ID or any struct with an ID as its first member.

A lot of code I've seen simply casts to the struct all over the place, but it seems it is possible to have a more reliable method.

Is there a way to check at compile time?


Note, for the purpose of this question, we can assume all structs use the same member name for the struct they inherit from.


Note, I was hoping to be able to use something like this...

#  define CHECK_TYPE_POLYMORPHIC(val, member, struct_name) \
    (void)(_Generic((*(val)), \
        /* base-struct */  struct_name: 0, \
        /* sub-struct */   default: (_Generic(((val)->member), struct_name: 0))))

/* --- snip --- */
/* check that `var` is an `ID`, or `var->id_base` is */
CHECK_TYPE_POLYMORPHIC(var, id_base, ID);

...but this fails for ID types in the default case - because they have no id member.

So far the only way I found to do this is to type-check against a complete list of all structs, which isn't ideal in some cases (may be many — or defined locally, therefore not known to the macro, see: Compile time check against multiple types in C?).

like image 336
ideasman42 Avatar asked Jul 13 '26 04:07

ideasman42


1 Answers

You shouldn't use casts. A cast supposes that you know what you are doing and in the worst case leads to undefined behavior. You'd have to rely on the fact that the types that you are interested in all have that struct ID field with the same name.

Then, in the case that you present where you actually have a do-while kind of functional macro, you can easily place an auxiliary variable:

#define ID_NAME_SET_AND_VALUE(id, name, val) \
    do {                                     \
        ID* _id = &((id)->id_base));         \
        id_name_set(_id, (name));            \
        _id->value = (val);                  \
    } while(0)

If all goes well, this is a nop, if not it is a constraint violation and aborts compilation.

In a context where you can't place a variable you could use a compound literal, something like

 (ID*){ &((id)->id_base)) }
like image 103
Jens Gustedt Avatar answered Jul 14 '26 17:07

Jens Gustedt