Jump to content
    

Можно ли заставить несколько составных литералов использовать одну и ту же память стека?

Имеем следующее

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?

Share this post


Link to post
Share on other sites

44 минуты назад, makc сказал:

Вроде оно, но... на 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).

:sad:

P.S. Вот еще нашел по теме: https://stackoverflow.com/questions/47691857/lifetime-of-a-compound-literal

Такая же проблема обсуждается.

Еще тут: https://github.com/llvm/llvm-project/issues/109204

Какой-то (видимо, разработчик LLVM) сказал, что займется этой проблемой в начале 2025 года:good: Правда, апрель уже...

Там же ссылка на: 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 им, видимо, придется вернуться к этому гордиеву узлу.

Share this post


Link to post
Share on other sites

А если скобочками обернуть каждый вызов?

{
    can_Send(CAN_BUS_1, &(sCANFrame){});
}
{
    can_Send(CAN_BUS_1, &(sCANFrame){});
}

 

 

Share this post


Link to post
Share on other sites

11 часов назад, Arlleex сказал:

Т.е. проблема известна, проблема действительно является проблемой, и, как я понял, она просто "исторически" так получилась. Комитет по Си с 99-го стандарта не менял поведение составных литералов, поэтому ребята немного забили на их поддержку. А с появлением C23 им, видимо, придется вернуться к этому гордиеву узлу.

Попробовал проверить -fstack-reuse в gcc-12 и там тоже никакого реюза не получается. Добавление вызовов с анонимной структурой в параметрах увеличивает размер кадра стека функции.

21 минуту назад, AHTOXA сказал:

А если скобочками обернуть каждый вызов?

GCC это не помогает, что вполне ожидаемо.

Share this post


Link to post
Share on other sites

35 минут назад, AHTOXA сказал:

А если скобочками обернуть каждый вызов?

{
    can_Send(CAN_BUS_1, &(sCANFrame){});
}
{
    can_Send(CAN_BUS_1, &(sCANFrame){});
}

CLang тоже не помогает, пробовал первым делом🙂

Share this post


Link to post
Share on other sites

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 тоже не помогает, пробовал первым делом🙂

Шлангом не буду пользоваться. По крайней мере пока. 🙂

Share this post


Link to post
Share on other sites

sCANFrame frame;

can_Send(CAN_BUS_1, &(frame=(sCANFrame){}));

can_Send(CAN_BUS_1, &(frame=(sCANFrame){}));

А с такими костылями?

Share this post


Link to post
Share on other sites

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

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

Share this post


Link to post
Share on other sites

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

навести "красоту" макросами, но это ужОс конечно.

Share this post


Link to post
Share on other sites

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

навести "красоту" макросами, но это ужОс конечно.

Не не, это можно, конечно, но это действительно ужос)) Не годится.

Share this post


Link to post
Share on other sites

работа с CAN?


Так оно должно сидеть не в stack, а в heap....
CAN слот разгружается в оперативку по прерыванию (DMA? или ещё как...) и освобождается место для принятия следующего мессага в тот же слот. Иначе ж тупик... слот же занят и не разгружен!
И пока вы обрабатываете один приём, следующий - если пришел сразу - уходит в никуда.

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

Ну а вся надстройка работает с теми данными, которые в оперативке.

Та же песня про отправку. А если слот занят? Или теряем данные (а там типа sequence increment и прочее на всеобщую рабость), или неоправданно ждём пока вдруг случится чудо...
Во всей структуре ещё один параметр нужен - флаг "на передачу", флаг на "новые данные поступили".... И всё сразу крутится как бы независимо. По прерываниям таймера... ибо CAN - всё же в основном циклическая передача данных.

А если из подпрограммы, из стека напрямую - ну так ждать пока слот освободится? Или как? 

Мож не в тему, просто мысли вслух.  
К вышеупомянутой проблеме относится лишь косвенно.

Share this post


Link to post
Share on other sites

9 минут назад, girts сказал:

работа с CAN?


Так оно должно сидеть не в stack, а в heap....
CAN слот разгружается в оперативку по прерыванию (DMA? или ещё как...) и освобождается место для принятия следующего мессага в тот же слот. Иначе ж тупик... слот же занят и не разгружен!
И пока вы обрабатываете один приём, следующий - если пришел сразу - уходит в никуда.

Я слишком начинающий, это очевидно🙂 Поэтому, разумеется, can_Send() добавляет фрейм в очередь отправки, которая разгребается по мере отправки в шину.

Share this post


Link to post
Share on other sites

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 вообщем....

Хз...

Share this post


Link to post
Share on other sites

1 час назад, girts сказал:

Ну и что что чуть больше памяти занимает....
Или - просто назовём это недостаточной оптимизацией.

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

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

1 час назад, girts сказал:

Я про сам подход - зачем такое применение нужно именно в вашем случае, наверное просто не догоняю...

Такое - это какое? Это самое типовое использование анонимных локальных объектов. Что у Вас вызывает вопросы?

Цитата

Если есть какой то фрейм, в котором передаются актуальные данные - если всё сложено в ожидание, что толку то от предыдущих данных, которые по какой то причине не ушли вовремя?

Почему они не уйдут вовремя? Кадры заталкиваются в очередь и сразу же направляются драйвером в сеть. Друг за другом. А Вы как себе это представляете по-другому?

Цитата

Если типа комманда, которая должна выполнится, тогда тем более. 
Если блок данных, то его размер известен, он приготовлен в оперативке. Одно сообщение передано - по факту приготавливаем следующее... с нужным индексом и всеми прочими подробностями.... ISO 15765-2 вообщем....

Хз...

Вот и я хз, о чем Вы говорите.

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...