LINUX.ORG.RU

Посоветуйте современную эмуляцию «классов» для С в эмбеды

 , ,


3

3

https://github.com/lvgl/lvgl/issues/1919

По ссылке я выписал основную литературу и библиотки. Там все толково, но не знаю насколько актуально.

Если кто в курсе, на чем нынче модно ООП для С изображать, дайте знать. Надо для эмбедов:

  • много оперативки жрать нельзя.
  • много флеша жрать не желательно.

По фичам критично только наследование методов/данных и virtual. Можно забить болт на private, эксепшены, множественое наследование и т.п.

Ответ типа «лучше ooc toolkit до сих пор ничего не придумали» - тоже устроит.

★★★★★

А почему собсно не использовать C++ ? Ну я к тому что киты так то есть, но не уверен что ручная имплементация таблиц виртуальных функций и runtime полиморфизм будут на выходе лучше, чем то что из коробки выдаёт компилятор C++

sparks ★★★ ()
Последнее исправление: sparks (всего исправлений: 1)

модно ООП

Модно писать как в Rust, благо это всё уже есть в С.

наследование => композиция

virtual => Trait object

никакой фреймворк не нужен, это встроено в С с самого начала. Но хипстеры только узнали…

fsb4000 ★★★★★ ()
Ответ на: комментарий от sparks

Мне не надо лучше или так же. Надо хоть как-то, потому что плюсы есть не подо все, а лепить а сях DOM совсем без ООП - перебор.

Vit ★★★★★ ()
Ответ на: комментарий от Vit

В чем проблема налепить функций, где первым аргументом (как минимум) будет всегда передаваться структура, над которой будут производиться манипуляции?

DOM

Ну, структура узла, поле с типом и вперде.

deep-purple ★★★★★ ()
Ответ на: комментарий от Vit

Это эмбеды, там не везде плюсы есть.

Где конкретно не поддерживается C++? Даже в примитивной Arduino на 8 битном AVR есть C++. В чём смысл использовать такие платформы?

Классы на C++ без виртуальных методов полностью эквивалентны обычным структурам и функциям на Си и первым аргументом - указателем на структуру. Виртуальные методы - это просто указатель в таблице виртуальных методов. В Си это делается примерно так же, только руками. Исключения и RTTI можно отключить в флагах компилятора.

X512 ★★★ ()
Ответ на: комментарий от Vit

а лепить а сях DOM

эмбеды без C++

You are doing it wrong.

X512 ★★★ ()
Ответ на: комментарий от Vit

а лепить а сях DOM совсем без ООП

В чем проблема? Уже несколько лет используем деревья на двунаправленных списках, брат жив.


    struct frs_json * coms = frs_json_find(data, "commands", FRS_JSON_OBJECT);
    struct frs_json * com  = frs_json_child(coms);
    while (com) {
        printf("%s\n", com->data.string);
        
        com = frs_json_next(com);
    }
PPP328 ★★★★ ()
Последнее исправление: PPP328 (всего исправлений: 1)
Ответ на: комментарий от Vit

ОПП - это:

  • Абстракция:

struct object {

}
  • Инкапсуляция:
struct object {
   u32 size;
}
void tell(struct object * this, const char * theword); // Собственно, как и устроены классы в С++
  • Наследование:

struct cube {
    struct object object;
}
  • Полиморфизм: (по интерпретации)
struct object {
    enum type type;
    union objects {
        struct cube cube;
        struct ball ball;
    }
}

(по перегрузке)

int func(enum type type, ...) {
    va_args args;
    va_start(args, type);
    switch (type) {
        CUBE : {
            printf("%s\n", "cube");
            printf(" size: %u\n", va_arg(args, u32));
            break;
        }
        BALL : {
            printf("%s\n", "ball");
            printf(" diameter: %u\n", va_arg(args, u32));
            printf(" name: %s\n", va_arg(args, const char *));
            break;
        }
    }

    va_end(args);
}
PPP328 ★★★★ ()

По ссылке я выписал основную литературу и библиотки. Там все толково, но не знаю насколько актуально.

пишешь просто на плюсах, отключаешь rtti и эксепшены, если размер озу поджимает, или хочется иметь полный контроль над кодом(из эстетических соображений) - можно написать свой микрорантайм(как это делаю я), и снести весь стандартный рантайм плюсов.

эмулировать классы на си это мутота мутот. писать сложней, а результат точно не лучше и почти точно хуже.

alysnix ()
Ответ на: комментарий от PPP328

Наследование: struct cube { struct object object; }

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

alysnix ()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от alysnix

Наследование, сейчас покажу:

#include <stdio.h>

struct object {
    int time;
};

struct cube {
    struct object object;
    int width;
};

struct ball {
    struct object object;
    int diameter;
};

static void print_object(struct object * object) {
    printf("time = %d\n", object->time);    
}

static void print_cube(struct cube * cube) {
    printf("width = %d\n", cube->width);
}

static void print_ball(struct ball * ball) {
    printf("diameter = %d\n", ball->diameter);
}

int main()
{
    struct object object = { .time = 5 };
    struct cube   cube   = { .object = { .time = 4 }, .width    = 5 };
    struct ball   ball   = { .object = { .time = 8 }, .diameter = 7 };
    
    print_object(&object);
    print_cube  (&cube);
    print_ball  (&ball);

    print_object((struct object *)&cube);
    print_object((struct object *)&ball);

    return 0;
}

time = 5                                                                                                                                                                        
width = 5                                                                                                                                                                       
diameter = 7                                                                                                                                                                    
time = 4                                                                                                                                                                        
time = 8 

Совместимость указателей никого не волнует, можете макрос каста сделать.

Факт в том, что cube и ball - расширяют функционал object, если он идет первым.

PPP328 ★★★★ ()
Последнее исправление: PPP328 (всего исправлений: 1)
Ответ на: комментарий от PPP328

Совместимость указателей никого не волнует, можете макрос каста сделать.

пусть наследование глубокое, ну там хотя бы три предка в иерархии. A <- B <- C <- D.

А - самый первый предок. как из функций класса D обращаться к полям класса A? ну понятно что можно, извращенчески перечислив все эти встраивания и добравшись до поля класса A. а как потом этот код сопровождать? если все будет слетать, стоит в иерархии что-то поменять.

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

    print_object((struct object *)&cube);
    print_object((struct object *)&ball);

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

alysnix ()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от alysnix

A <- B <- C <- D

Если мне не изменяет память, то трюк именно в том чтобы расположить родительскую структуру первой в дочерней структуре, тогда схема D должна выглядеть как-то так:

[ A x y z u v ] B a b c ] C ...     ] D r g b ]
^                       ^           ^
|                       |           |
указатель p здесь       B(*p)       C(*p)

как из функций класса D обращаться к полям класса A?

(A*)(this)->x?

Я не помню все эти премудрости в деталях, это псевдокод, но как-то так это и делается.

По теме: документация к OOC выглядит немного запущенной, у них на странице указан std::auto_ptr, его из С++ уже успели удалить. Мне казалось, что лет 10 назад, но на самом деле 10 лет назад он стал deprecated, а удалили его года 3 назад. Возможно стоит глянуть в историю комитов, когда там были последние изменения.

aleksey_tulinov ()
Последнее исправление: aleksey_tulinov (всего исправлений: 1)

Извратись. Возьми vala, это ООП язык, но его компилятор гонит его в сишный код, а потом собирает gcc. Но это только если у тебя совсем всё жирно по железкам и ХЗ, насколько практично. По идее сишный код ты тоже подёргать из него должен мочь.

ЗЫ

Ещё можно C++ (да и другой любой ЯП) clang-ом собирать в байткод и llc в сишные портянки преобразовывать. Но оно совсем нечитабельно будет, но будет собираться назад, скорее всего нормально.

Но тут надо смотреть какие фичи могут быть сгенерированы и какие фичи реально есть на твоей целевой платформе.

peregrine ★★★★★ ()
Последнее исправление: peregrine (всего исправлений: 2)

Ну мне как-то понадобилось, вот залил мануальчик, который сильно помог, хоть и довольно старый. Но, в принципе, если догнать концепцию, то на современном Си можно и поупростить.

upd. Вспомнил, что-то на более современной Си-шке было у Бен Клеменс «Язык С в 21-веке».

SkyMaverick ★★★★ ()
Последнее исправление: SkyMaverick (всего исправлений: 2)
Ответ на: комментарий от monk

С чего это? (struct object *)cube == &(cube->object)

с явным и хардкорным преобразованием типа совместимо вообще все.

int i; float * pf = (float * )&i;

имелась ввиду совместимость без преобразования типа. а преобразование типа в си непроверяемо, и компилятор считает, что пацан знает что делает. а пацан может и не знать или ошибиться в типе.

alysnix ()

Можно полностью руками эмулировать ООП. Но это будет не так удобно, как в с++.

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

pathfinder ★★★★ ()
Ответ на: комментарий от alysnix

с явным и хардкорным преобразованием типа совместимо вообще все.

Ты разницу между оригинальным и твоим примерами вообще не видишь?

Siborgium ★★★★ ()
Ответ на: комментарий от SkyMaverick

Первую книжку я читал, она же и првая по мой ссылке. Вторую гляну. Общий принцип не секретный - данные «наследуем» как в сях заведено, а за методами бегаем в «дескриптор класса», который кладем сбоку.

У меня концептуальный вопрос. Почему методы шпилятся на .vtable, а не на сам дескриптор напрямую?

// c vtable
MyClass(object->class_ref)->vtable->my_method()

// напрямую
MyClass(object->class_ref)->my_method()

Есть какие-то проблемы с тем чтобы такое организовать?

Vit ★★★★★ ()
Ответ на: комментарий от Vit

Есть какие-то проблемы с тем чтобы такое организовать?

Посчитай размер экземпляра класса когда у нас лишь один vtable, и когда у нас n виртуальных методов напрямую

fsb4000 ★★★★★ ()
Последнее исправление: fsb4000 (всего исправлений: 2)
Ответ на: комментарий от fsb4000

Речь о дескрипторе (самом классе), а не об истансе. Посмотри ooc toolkit хотя бы, ты очень мимо пишешь.

Vit ★★★★★ ()
Ответ на: комментарий от Vit

Посмотри ooc toolkit хотя бы, ты очень мимо пишешь.

Давай конкретные цитаты и страницы, нет времени читать муть.

vtable используется для уменьшения размера, чтобы не плодить копии того что уже есть. Если там этого нет, то это косяк ooc. Точка.

fsb4000 ★★★★★ ()
Ответ на: комментарий от fsb4000

Завязывай с дартаньянством. Ты сам влез в тему, никто силой не тащил. Влом вникнуть в контекст вопроса - никто постить не заставляет. Капитанские советы мне без надобности.

Vit ★★★★★ ()
Ответ на: комментарий от fsb4000

vtable используется для уменьшения размера

Ну ещё полиморфизм (даже Си-закос под него), я себе слабо представляю, как без vtables можно реализовать.

SkyMaverick ★★★★ ()
Последнее исправление: SkyMaverick (всего исправлений: 1)
Ответ на: комментарий от Vit

Понятно, сам слился, так как ни черта не знаешь. Знал бы, то расписал свой древний ooc.

Пришёл просить помощи, то и веди себя достойно.

В этой теме только @PPP328 и я показали код.

Ещё @alysnix начал делать ревью.

Остальные пишут «капитанские» советы.

Но знаешь, наверное я тебя заигнорю, как неадеквата. Изучай ООП по книге 1993 года, всего хорошего.

fsb4000 ★★★★★ ()
Ответ на: комментарий от SkyMaverick

Ну ещё полиморфизм (даже Си-закос под него), я себе слабо представляю, как без vtables можно реализовать.

Хранить указатели на функции,а не vtable.

Если нужно одну-две виртуальных функций, то это даже будет сравнимо с vtable. Так как размер будет примерно такой же, но на одно косвенное обращение меньше.

fsb4000 ★★★★★ ()
Ответ на: комментарий от fsb4000

Ты здесь бесполезен чуть более чем полностью. Вали козе в трещину.

Vit ★★★★★ ()
Ответ на: комментарий от Vit

Есть какие-то проблемы с тем чтобы такое организовать?

vtable - singleton.

Экземпляры класса хранят только 8байтный указатель на singleton, а не каждый свою одинаковую копию таблицы.

PPP328 ★★★★ ()
Ответ на: комментарий от PPP328

Ты не совсем понял вопрос. Экземпляр класса хранит указатель (object->class_ref) на дескриптор класса, где мы можем извлекать инфу о методах, наследовании и т.п. Грубо говоря - еще одна структура сбоку лежит, единая для всех классов (как ты и сказал).

Но почему-то внутри этой структуры методы кладутся в отдельный под-список, а не миксятся на верхний уровень. Из-за этого вызов выглядит длиннее.

Вопрос был, нафига авторы библиотеки так делали :). Или возможно я что-то не так понял.

Vit ★★★★★ ()
Ответ на: комментарий от slovazap

Не ко мне вопрос. Я не вникал чего там наопрашивали. Есть проект, где я не влияю на язык, но все согласны что есть прокол в архитектуре который надо закрыть.

Vit ★★★★★ ()
Ответ на: комментарий от Vit

Вопрос был, нафига авторы библиотеки так делали :).

Адекватные люди таких библиотек вообще делать не будут. Ничего не мешает хранить указатели на виртуальные методы напрямую в дескрипторе класса. В C++ именно так и сделано. В C++ Itanium ABI (GCC, clang) дескриптор класса находится по смещению 0 и содержит указатели на виртуальные методы а также на type_info.

X512 ★★★ ()
Ответ на: комментарий от fsb4000

Какое красивое использование designated initializers... Спасибо за код, всё понятно.

Добавлю, что вот эти функции

static double SquareAreaForShape(void const* v)
{
    Square const* s = (Square const*)v;
    return SquareArea(s);
}
static double CircleAreaForShape(void const* v)
{
    Circle const* s = (Circle const*)v;
    return CircleArea(s);
}
можно обернуть в какой-нибудь макрос, дабы автоматизировать создание виртуальных функций из обычных.

И эти функции тоже

static IShape SquareAsShape(Square const* s)
{
    IShape result = {.data=s
    ,.table=&squareMethods
    };
    return result;
}
static IShape CircleAsShape(Circle const* s)
{
    IShape result = {.data=s
    ,.table=&circleMethods
    };
    return result;
}
Будет почти так же «чистенько», как в C++

Crocodoom ★★★★ ()
Ответ на: комментарий от Crocodoom

Какое красивое использование designated initializers… Спасибо за код, всё понятно.

Тоже хотел похвалить. Хороший код, прям проникся духом ООП, так сказать.

@fsb4000 +1

goto-vlad ()
Ответ на: комментарий от Crocodoom

Будет почти так же «чистенько», как в C++

Я именно написал что модный, потому что такой подход использует даже в С++ вместо родных virtual/наследования

https://github.com/ldionne/dyno

И на конференциях тоже: https://www.youtube.com/watch?v=8c6BAQcYF_E

Но мне родные C++ классы тоже норм, я не настолько радикален в этом :)

fsb4000 ★★★★★ ()
Последнее исправление: fsb4000 (всего исправлений: 1)
Ответ на: комментарий от X512

Ну может там какие-то траблы с тем, чтобы трансформировать human-friendly описание класса в конечный результат (где указатели на функции уже выставлены с учетом наследования).

Я поэтому и спрашиваю, могли быть причины или нет. Может, например, автор хотел явно отделить виртуальные методы от остальных. Фик знает, в чужую голову же не залезешь.

Vit ★★★★★ ()
Ответ на: комментарий от Vit

ссылка на дескриптор класса нужна скорее всего для какого-то подобия rtti, ссылка на класс скорее всего содержит ссылку на дескриптор класса предка, чтобы в рантайме проверить наследуется ли данный класс от другого. ну и дескриптор несет еще какую-то инфу. а vmt содержит виртуальные методы доступные классу.

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

короче это не принципиально, вопрос в том, что они в свое ооп заложили там.

alysnix ()
Ответ на: комментарий от alysnix

ссылка на класс скорее всего содержит ссылку на дескриптор класса предка

В C++ ABI наоборот указатель на дескриптор типа находится в vtable потому что к vtable обычно требуется более быстрый доступ чем к информации о типе. Видел реализацию ООП где в дескрипторе типа также хранятся указатели на виртуальные методы по отрицательным смещениям.

X512 ★★★ ()
Ответ на: комментарий от Vit

Что значит не к тебе, если именно ты начал тему, и ты же заявил что «нет плюсов»?

Есть проект, где я не влияю на язык

Ты что там, раб, или просто сочувствующий? Кто как не разработчик напрямую влияет на проект в котором работает?

но все согласны что есть прокол в архитектуре который надо закрыть

И почему не использовать плюсы, если

  • все согласны
  • задача заключается в использовании фич плюсов
  • и закрывается она самым простым и очевидным образом плюсами?

Если у вас там не знают что плюсы бывают без std и исключений и можно начать с того что скомпилить тот же самый код плюсовым компилятором и получить с большой вероятностью идентичный бинарник (а в некоторых случаях ещё и более оптимальный), ну так открой глаза тем кро у вас «решает» и сразу напиши докладную начальству что решают почему-то вообще не те люди.

slovazap ★★★★★ ()
Последнее исправление: slovazap (всего исправлений: 1)
Ответ на: комментарий от X512

В C++ ABI наоборот указатель на дескриптор типа находится в vtable потому что к vtable обычно требуется более быстрый доступ чем к информации о типе

это по взрослому.

я лишь хочу подчеркнуть, что для базового ооп с виртуальными методами надо только иметь в обьекте ссылку на vmt. это если не наворачивать rtti. а если наворачивать - то да, ссылка на vmt, и в ней ссылка на дескриптор класса явно оптимальней.

alysnix ()
Ответ на: комментарий от alysnix

rtti надо обязательно, а вот всякую изоляцию внутри классов - нет. Если все будет а ля «virtual» + «public» - сойдет.

Тогда остается вопрос, как покрасивше организовать декларацию данных и методов для классов.

Vit ★★★★★ ()
Ответ на: комментарий от slovazap

Что значит не к тебе, если именно ты начал тему, и ты же заявил что «нет плюсов»?

Я описал граничные условия, в рамках которых есть вопрос. Почему нет плюсов - решали другие люди. Типа, разделение труда, зоны ответствености и все такое, в курсе? Я достаточно компетентен, чтобы уметь работать с тем что есть вместо того что хочется, и не лезть не в свои дела.

Vit ★★★★★ ()

вариант «не выёживаться» и использовать в базе С++ (как С с классами + stl) чем не устраивает ? вам модно-молодёжно или всё таки ехать ?

а из всех ООП на С без спец-языка есть только gnome.

как вариант - lisp,tcl на верхнем уровне и C на специфике железа. Это покруче плюсов по возможностям, но есть свои НО

MKuznetsov ★★★★★ ()
Ограничение на отправку комментариев: только для зарегистрированных пользователей