Есть ли способ сделать GCC / Clang осведомленным о наследовании в C?

Я пишу библиотеку C, которая использует некоторое простое объектно-ориентированное наследование:

struct Base { int x; }; struct Derived { struct Base base; int y; }; 

И теперь я хочу передать Derived * в функцию, которая принимает Base *, примерно так:

 int getx(struct Base *arg) { return arg->x; }; int main() { struct Derived d; return getx(&d); }; 

Это работает и, разумеется, является типичным, но компилятор этого не знает. Есть ли способ сообщить компилятору, что это тип? Я сосредотачиваюсь только на GCC и clang здесь, поэтому ответы на конкретные компиляторы приветствуются. У меня смутные воспоминания о том, как увидеть какой-то код, который сделал это, используя __attribute__((inherits(Base)) или что-то в этом роде, но моя память может лежать.

    Это безопасно в C, за исключением того, что вы должны указать аргумент Base * . Правило, запрещающее наложение псевдонимов (или, точнее, исключающее его поддержку в стандарте C), приведено в C 2011 6.5, где в пункте 7 говорится:

    Объект должен иметь сохраненное значение, к которому обращается только выражение lvalue, которое имеет один из следующих типов:

    – тип, совместимый с эффективным типом объекта,

    Это правило не позволяет нам вводить указатель на float , преобразовывать его в указатель на int и разыменовывать указатель на int для доступа к float как int . (Точнее, это не мешает нам пытаться, но это делает поведение неопределенным.)

    Может показаться, что ваш код нарушает это, поскольку он обращается к объекту Derived с использованием Base lvalue. Однако преобразование указателя в Derived в указатель на Base поддерживается C 2011 6.7.2.1 в пункте 15 говорится:

    … Указатель на объект структуры, соответствующим образом преобразованный, указывает на его начальный член …

    Итак, когда мы преобразуем указатель на Derived в указатель на Base , то, что мы на самом деле имеем, не является указателем на объект Derived с использованием другого типа, чем это (что запрещено), но указатель на первый элемент объекта Derived используя его фактический тип Base , который отлично подходит.

    Об изменении: Первоначально я указал, что аргументы функции будут преобразованы в типы параметров. Однако C 6.5.2.2 2 требует, чтобы каждый аргумент имел тип, который может быть назначен объекту с типом его соответствующего параметра (с любыми квалификациями, такими как const удаленный), и 6.5.16.1 требует, чтобы при назначении одного указателя другому , они имеют совместимые типы (или соответствуют другим условиям, не применимым здесь). Таким образом, передача указателя на Derived на функцию, которая принимает указатель на Base нарушает стандартные ограничения C. Однако, если вы выполняете преобразование самостоятельно, это законно. При желании преобразование может быть встроено в макрос препроцессора, который вызывает функцию, так что код по-прежнему выглядит как простой вызов функции.

    Укажите адрес базового элемента (по-настоящему безопасный тип):

     getx(&d.base); 

    Или используйте указатель void:

     int getx(void * arg) { struct Base * temp = arg; return temp->x; }; int main() { struct Derived d; return getx(&d); }; 

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

    Как отмечено выше пользователем694733, вам, вероятно, лучше всего соответствовать стандартам и типу безопасности, используя адрес базового поля как в (повторяющийся для дальнейшего использования)

     struct Base{ int x; } struct Derived{ int y; struct Base b; /* look mam, not the first field! */ } struct Derived d = {0}, *pd = &d; void getx (struct Base* b); 

    и теперь, несмотря на то, что база не является первым полем, которое вы все еще можете сделать

     getx (&d.b); 

    или если вы имеете дело с указателем

     getx(&pd->b). 

    Это очень распространенная идиома. Вы должны быть осторожны, если указатель NULL, однако, потому что & pd-> b просто делает

     (struct Base*)((char*)pd + offsetof(struct Derived, b)) 

    поэтому & ((Производный *) NULL) -> b становится

     ((struct Base*)offsetof(struct Derived, b)) != NULL. 

    ИМО – это упущенная возможность того, что C принял анонимные структуры, но не принял анонимную структурную модель plan9, которая

     struct Derived{ int y; struct Base; /* look mam, no fieldname */ } d; 

    Он позволяет вам просто написать getx (& d), и компилятор отрегулирует указатель Derived на базовый указатель, то есть в приведенном выше примере он будет точно таким же, как getx (& d.b). Другими словами, он эффективно дает вам наследование, но имеет очень конкретную модель макета памяти. В частности, если вы настаиваете на том, чтобы не встраивать (== inheriting) базовую структуру вверху, вам приходится иметь дело с NULL самостоятельно. Поскольку вы ожидаете от наследования, оно работает рекурсивно, поэтому для

     struct TwiceDerived{ struct Derived; int z; } td; 

    вы все равно можете написать getx (& td). Кроме того, вам может не понадобиться getx, поскольку вы можете написать dx (или td.x или pd-> x).

    Наконец, используя расширение типа gcc, вы можете написать небольшой макрос для downcasting (т. Е. Листинг для более производной структуры)

     #define TO(T,p) \ ({ \ typeof(p) nil = (T*)0; \ (T*)((char*)p - ((char*)nil - (char*)0)); \ }) \ 

    так что вы можете делать такие вещи, как

     struct Base b = {0}, *pb = &b; struct Derived* pd = TO(struct Derived, pb); 

    что полезно, если вы пытаетесь выполнять виртуальные функции с указателями функций.

    На gcc вы можете использовать / экспериментировать с плановыми 9 расширениями с расширениями -fplan9. К сожалению, он, похоже, не был реализован на clang.