Проверка времени компиляции полиморфных типов в C?

Полиморфные структуры довольно распространены в C, но часто включают явное приведение типов, позволяющее случайно преобразовать несовместимые структуры.

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

Проблема здесь в том, что мы не можем проверить тип аргумента id в макросе на соответствие одному типу, так как это может быть ID или любая структура с ID в качестве первого члена.

Много кода, который я видел, просто везде приводит к структуре, но кажется, что можно иметь более надежный метод.

Есть ли способ проверить во время компиляции?


Обратите внимание, что для целей этого вопроса мы можем предположить, что все структуры используют одно и то же имя члена для структуры, от которой они наследуются.


Заметьте, я надеялся, что смогу использовать что-то подобное...

#  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);

... но это не работает для типов ID в случае default, потому что у них нет члена id.

Пока что единственный способ, который я нашел для этого, — это проверка типов по полному списку всех структур, что в некоторых случаях не идеально (их может быть много — или они определены локально, поэтому не известны макросу, см.: Проверка времени компиляции для нескольких типов в C?).


person ideasman42    schedule 24.09.2014    source источник
comment
Я не могу отделаться от ощущения, что это движется по неверному пути. Если вы хотите C++, используйте C++.   -  person Jonathan Leffler    schedule 24.09.2014
comment
@ Джонатан Леффлер. Я понимаю, что, возможно, я пытаюсь сделать что-то, что на самом деле не поддерживается C, но я не очень заинтересован в переходе на другой язык, если невозможно проверить тип полиморфных структур, я могу обойтись проверкой по списку структур уже, но если можно добавить некоторые дополнительные проверки типов в существующую кодовую базу C, то почему бы и нет?   -  person ideasman42    schedule 24.09.2014
comment
Читали ли вы какую-либо документацию по GObject или gtk+2.0/gtk+3.0 . Они достигают этого, не изобретая заново велосипед. Просто подражайте их коду, это авторское право gnu lesser gpl.   -  person phil    schedule 26.09.2014
comment
@rhubarbdog - есть ли конкретное ключевое слово/макрос, которое нужно искать в связи с этим?   -  person ideasman42    schedule 26.09.2014
comment
не знаю, но общий тип GtkWidget постоянно переопределяется, скажем, макросом GTK_BUTTON, чтобы это работало, вы должны скомпилировать с правильными предупреждениями, -Wall делает это.   -  person phil    schedule 26.09.2014


Ответы (2)


Вы не должны использовать приведения. Приведение предполагает, что вы знаете, что делаете, и в худшем случае приводит к неопределенному поведению. Вам придется полагаться на тот факт, что интересующие вас типы имеют поле struct ID с одинаковым именем.

Затем, в случае, если вы представляете, где у вас действительно есть функциональный макрос типа do-while, вы можете легко разместить вспомогательную переменную:

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

Если все идет хорошо, это nop, если нет, то это нарушение ограничения и прерывание компиляции.

В контексте, где вы не можете поместить переменную, вы можете использовать составной литерал, что-то вроде

 (ID*){ &((id)->id_base)) }
person Jens Gustedt    schedule 24.09.2014

Самое близкое в C11 (последний стандарт C) для компиляции- полиморфизм time — это общие для типов выражения с использованием ключевого слова _Generic, но я не уверен, что он соответствует вашим потребностям.

Компилятор GCC также предоставляет вам его __builtin_type_compatible_p, с помощью которого вы можете построить, например некоторые макросы.

Вы также можете настроить GCC с некоторыми расширениями MELT.

person Basile Starynkevitch    schedule 24.09.2014