简体   繁体   中英

Compile time check polymorphic types in C?

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? ).

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)) }

The closest thing in C11 (the latest C standard) for compile-time polymorphism is its type-generic expressions using the _Generic keyword, but I am not sure it fits your needs.

The GCC compiler also gives you its __builtin_type_compatible_p with which you could build eg some macros.

You could also customize GCC with some MELT extensions

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM