Arlleex 346 April 10, 2025 Posted April 10, 2025 · Report post Имеем следующее typedef struct { u32 id : 29, ext : 1, rtr : 1, dlc; union { u8 byte[8]; u16 half[4]; u32 word[2]; }; } sCANFrame; s32 can_Send(eCANBus canBus, sCANFrame const *frame); Частенько вместо прямого создания объекта sCANFrame, заполнения его, и вызова can_Send(), я прибегаю к использованию составных литералов can_Send(CAN_BUS_1, &(sCANFrame){ .id = DEV_CAN_TX_ID, .ext = CAN_ID_EXT, .rtr = 0, .dlc = 8, .word[0] = 0xA, .word[1] = 0xB }); Т.е. здесь в стеке создается анонимный объект типа sCANFrame и передается функции отправки. Однако, если я в ряд расположу множество (>1) таких конструкций, листинг показывает, что начальная резервация стека увеличивается как раз на N * sizeof (sCANFrame) + конечное выравнивание. Т.е. ничего не мешает компилятору использовать ту же память стека под составные литералы последующих вызовов, однако компилятор так не делает. Может, есть какой-то ключик в CLang/GCC? Quote Share this post Link to post Share on other sites More sharing options...
makc 387 April 10, 2025 Posted April 10, 2025 · Report post https://gcc.gnu.org/onlinedocs/gcc-7.1.0/gcc/Code-Gen-Options.html#Code-Gen-Options -fstack-reuse это не оно? Quote Share this post Link to post Share on other sites More sharing options...
Arlleex 346 April 10, 2025 Posted April 10, 2025 · Report post 44 минуты назад, makc сказал: https://gcc.gnu.org/onlinedocs/gcc-7.1.0/gcc/Code-Gen-Options.html#Code-Gen-Options -fstack-reuse это не оно? Вроде оно, но... на CLang не работает(( Но вот что заметил. Даже если компилировать вот так sCANFrame frame, frame1; can_Send(CAN_BUS_1, &frame); can_Send(CAN_BUS_1, &frame1); вне любых вложенных блоков, то статический анализ стека окружающей функции показывает 80 байт (цифра - просто цифра, т.к. тестирую на каком-то своем проекте, лень все выпиливать). Можно добавлять хоть 100 таких объектов, все они лягут в одну память стека, т.е. анализатор покажет те же 80 байт. При этом если этот кусок поменять на can_Send(CAN_BUS_1, &(sCANFrame){}); can_Send(CAN_BUS_1, &(sCANFrame){}); то при прочих равных показывает уже 116 байт. Добавление одной такой строчки будет увеличивать расход еще на 16 байт (на размер sCANFrame). P.S. Вот еще нашел по теме: https://stackoverflow.com/questions/47691857/lifetime-of-a-compound-literal Такая же проблема обсуждается. Еще тут: https://github.com/llvm/llvm-project/issues/109204 Какой-то (видимо, разработчик LLVM) сказал, что займется этой проблемой в начале 2025 года Правда, апрель уже... Там же ссылка на: https://github.com/llvm/llvm-project/issues/68746 Цитата Еще одна вещь, которую следует иметь в виду относительно составных литералов в C, заключается в том, что мы реализуем их способом, который является проблематичным для C23. Мы реализуем составные литералы так, как будто мы высвобождаем память как по волшебству, а затем получаем наше lvalue, но модель в стандарте C такова, как будто есть объявленный объект, а не просто область хранения. Это еще важнее теперь, когда у нас есть спецификаторы классов хранения для составных литералов: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3038.htm — какой бы подход мы в конечном итоге ни выбрали для решения этой проблемы для автоматики, следует помнить, что в ближайшем будущем нам понадобится поддержка статических и thread_local составных литералов. Я думаю, нам следует изучить изменение составных литералов, чтобы вместо этого они были a DeclRefExpr, которые ссылаются на a VarDeclс придуманным именем. Тогда спецификаторы класса хранения будут поддерживаться тривиально — поместите спецификатор класса хранения на неименованную переменную и выдайте ее так же, как и любую другую переменную. Т.е. проблема известна, проблема действительно является проблемой, и, как я понял, она просто "исторически" так получилась. Комитет по Си с 99-го стандарта не менял поведение составных литералов, поэтому ребята немного забили на их поддержку. А с появлением C23 им, видимо, придется вернуться к этому гордиеву узлу. Quote Share this post Link to post Share on other sites More sharing options...
AHTOXA 25 April 11, 2025 Posted April 11, 2025 · Report post А если скобочками обернуть каждый вызов? { can_Send(CAN_BUS_1, &(sCANFrame){}); } { can_Send(CAN_BUS_1, &(sCANFrame){}); } Quote Share this post Link to post Share on other sites More sharing options...
makc 387 April 11, 2025 Posted April 11, 2025 · Report post 11 часов назад, Arlleex сказал: Т.е. проблема известна, проблема действительно является проблемой, и, как я понял, она просто "исторически" так получилась. Комитет по Си с 99-го стандарта не менял поведение составных литералов, поэтому ребята немного забили на их поддержку. А с появлением C23 им, видимо, придется вернуться к этому гордиеву узлу. Попробовал проверить -fstack-reuse в gcc-12 и там тоже никакого реюза не получается. Добавление вызовов с анонимной структурой в параметрах увеличивает размер кадра стека функции. 21 минуту назад, AHTOXA сказал: А если скобочками обернуть каждый вызов? GCC это не помогает, что вполне ожидаемо. Quote Share this post Link to post Share on other sites More sharing options...
Arlleex 346 April 11, 2025 Posted April 11, 2025 · Report post 35 минут назад, AHTOXA сказал: А если скобочками обернуть каждый вызов? { can_Send(CAN_BUS_1, &(sCANFrame){}); } { can_Send(CAN_BUS_1, &(sCANFrame){}); } CLang тоже не помогает, пробовал первым делом🙂 Quote Share this post Link to post Share on other sites More sharing options...
makc 387 April 11, 2025 Posted April 11, 2025 · Report post 1 минуту назад, Arlleex сказал: CLang тоже не помогает, пробовал первым делом🙂 Осталось проверить, как себя ведёт компилятор от MS. 🙂 Проверил MSVC из Visual Studio 2019 - склеивает. Стек не раздувает. Сейчас посмотрел, что делает gcc 12 при запуске со следующими ключами: gcc -Os -g -S test.c -fstack-reuse=all -fstack-usage И получается, что он тоже вполне успешно повторно использует куски стека от уже не нужных структур. Так что я был несправедлив в gcc. 15 минут назад, Arlleex сказал: CLang тоже не помогает, пробовал первым делом🙂 Шлангом не буду пользоваться. По крайней мере пока. 🙂 Quote Share this post Link to post Share on other sites More sharing options...
_pv 111 April 11, 2025 Posted April 11, 2025 · Report post sCANFrame frame; can_Send(CAN_BUS_1, &(frame=(sCANFrame){})); can_Send(CAN_BUS_1, &(frame=(sCANFrame){})); А с такими костылями? Quote Share this post Link to post Share on other sites More sharing options...
Arlleex 346 April 11, 2025 Posted April 11, 2025 · Report post 7 часов назад, _pv сказал: sCANFrame frame; can_Send(CAN_BUS_1, &(frame=(sCANFrame){})); can_Send(CAN_BUS_1, &(frame=(sCANFrame){})); А с такими костылями? А как это Вы адрес r-value берете так?🙂 Если только вот так sCANFrame frame; frame=(sCANFrame){}; can_Send(CAN_BUS_1, &frame); frame=(sCANFrame){}; can_Send(CAN_BUS_1, &frame); Но уже не красиво... Да, стек уже не расходуется почем зря, но исчезают все плюсы синтаксического сахара, даваемого литералом. Quote Share this post Link to post Share on other sites More sharing options...
_pv 111 April 11, 2025 Posted April 11, 2025 · Report post 38 minutes ago, Arlleex said: А как это Вы адрес r-value берете так?🙂 да, что-то не подумал. sCANFrame frame; #define FRAME(...) ((frame=(sCANFrame){__VA_ARGS__}),&frame) can_Send( FRAME(.id=1, .byte={0}) ); can_Send( FRAME(.id=2, .byte={0}) ); #undef FRAME навести "красоту" макросами, но это ужОс конечно. Quote Share this post Link to post Share on other sites More sharing options...
Arlleex 346 April 11, 2025 Posted April 11, 2025 · Report post 3 минуты назад, _pv сказал: да, что-то не подумал. sCANFrame frame; #define FRAME(...) ((frame=(sCANFrame){__VA_ARGS__}),&frame) can_Send( FRAME(.id=1, .byte={0}) ); can_Send( FRAME(.id=2, .byte={0}) ); #undef FRAME навести "красоту" макросами, но это ужОс конечно. Не не, это можно, конечно, но это действительно ужос)) Не годится. Quote Share this post Link to post Share on other sites More sharing options...
girts 27 April 11, 2025 Posted April 11, 2025 · Report post работа с CAN? Так оно должно сидеть не в stack, а в heap.... CAN слот разгружается в оперативку по прерыванию (DMA? или ещё как...) и освобождается место для принятия следующего мессага в тот же слот. Иначе ж тупик... слот же занят и не разгружен! И пока вы обрабатываете один приём, следующий - если пришел сразу - уходит в никуда. Если это может быть критично и принимается всё, вообще делают по два слота на приём, и чтобы попеременно разгружались. Чтобы следующий не застрял и не попал в утиль. Ну а вся надстройка работает с теми данными, которые в оперативке. Та же песня про отправку. А если слот занят? Или теряем данные (а там типа sequence increment и прочее на всеобщую рабость), или неоправданно ждём пока вдруг случится чудо... Во всей структуре ещё один параметр нужен - флаг "на передачу", флаг на "новые данные поступили".... И всё сразу крутится как бы независимо. По прерываниям таймера... ибо CAN - всё же в основном циклическая передача данных. А если из подпрограммы, из стека напрямую - ну так ждать пока слот освободится? Или как? Мож не в тему, просто мысли вслух. К вышеупомянутой проблеме относится лишь косвенно. Quote Share this post Link to post Share on other sites More sharing options...
Arlleex 346 April 11, 2025 Posted April 11, 2025 · Report post 9 минут назад, girts сказал: работа с CAN? Так оно должно сидеть не в stack, а в heap.... CAN слот разгружается в оперативку по прерыванию (DMA? или ещё как...) и освобождается место для принятия следующего мессага в тот же слот. Иначе ж тупик... слот же занят и не разгружен! И пока вы обрабатываете один приём, следующий - если пришел сразу - уходит в никуда. Я слишком начинающий, это очевидно🙂 Поэтому, разумеется, can_Send() добавляет фрейм в очередь отправки, которая разгребается по мере отправки в шину. Quote Share this post Link to post Share on other sites More sharing options...
girts 27 April 11, 2025 Posted April 11, 2025 · Report post 3 hours ago, Arlleex said: Я слишком начинающий, это очевидно Совсем не про то..... "There does not seem to be any good reason for this. I would just call it a compiler bug." - там про это всё сказали. Критично или не критично - по сути результат один и тот же. Ну и что что чуть больше памяти занимает.... Или - просто назовём это недостаточной оптимизацией. Компилятор не ясновидящий, ему наверное какая то поочередность важна в создании обьектов и переменных, не видит он общую картину и того что больше те данные нигде не используются. А тут по его представлениям "исскуственного интеллекта" сначала как бы функция, и в ней инициализация переменных, или инициализация переменных и вызов функции, от чего и весь кипиш и диффаренс. 3 hours ago, Arlleex said: Поэтому, разумеется, can_Send() добавляет фрейм в очередь отправки, которая разгребается по мере отправки в шину. Я про сам подход - зачем такое применение нужно именно в вашем случае, наверное просто не догоняю.... Но учусь, и стараюсь понять! Если есть какой то фрейм, в котором передаются актуальные данные - если всё сложено в ожидание, что толку то от предыдущих данных, которые по какой то причине не ушли вовремя? Если типа комманда, которая должна выполнится, тогда тем более. Если блок данных, то его размер известен, он приготовлен в оперативке. Одно сообщение передано - по факту приготавливаем следующее... с нужным индексом и всеми прочими подробностями.... ISO 15765-2 вообщем.... Хз... Quote Share this post Link to post Share on other sites More sharing options...
Arlleex 346 April 11, 2025 Posted April 11, 2025 · Report post 1 час назад, girts сказал: Ну и что что чуть больше памяти занимает.... Или - просто назовём это недостаточной оптимизацией. Я уверен, и подтвердил свою мысль о баге среди других источников, и надеюсь, что этот баг исправят, ибо это очевидный косяк. Насчет "ну и что что чуть больше памяти занимает" - крайне не согласен, ибо в одной функции у меня формируются спискок рассылки порядка нескольких десятков фреймов разом. А эта функция вызывает еще подфункции, в которой тоже весьма объемный список рассылки. Поэтому такая недооптимизация сильно влияет на стековое потребление, стек итак не резиновый. 1 час назад, girts сказал: Я про сам подход - зачем такое применение нужно именно в вашем случае, наверное просто не догоняю... Такое - это какое? Это самое типовое использование анонимных локальных объектов. Что у Вас вызывает вопросы? Цитата Если есть какой то фрейм, в котором передаются актуальные данные - если всё сложено в ожидание, что толку то от предыдущих данных, которые по какой то причине не ушли вовремя? Почему они не уйдут вовремя? Кадры заталкиваются в очередь и сразу же направляются драйвером в сеть. Друг за другом. А Вы как себе это представляете по-другому? Цитата Если типа комманда, которая должна выполнится, тогда тем более. Если блок данных, то его размер известен, он приготовлен в оперативке. Одно сообщение передано - по факту приготавливаем следующее... с нужным индексом и всеми прочими подробностями.... ISO 15765-2 вообщем.... Хз... Вот и я хз, о чем Вы говорите. Quote Share this post Link to post Share on other sites More sharing options...