LINUX.ORG.RU

С/CPP можно ли закодить yeld через longjmp?

 , ,


1

2

Мне нужно написать функцию с длинной логикой, которая иногда «примораживается» до следующего тика таймера. Фактически - генератор, без необходимости заботиться о локальных переменных. Через switch получается уродски.

Нашел вот такой пример https://gist.github.com/mfoliveira/957805, он хорош, но работает только с GCC. Можно ли вместо указателей на метки использовать setjmp/longjmp? Вот так:

#define yield_start() if (YIELD_ENV != NULL) longjmp(YIELD_ENV, 1);

#define yield(val) if (!setjmp(YIELD_ENV)) return val;

#define yield_end() YIELD_ENV = NULL;

В спеке сказано, что если функция, вызвавшая setjmp() завершилась, то поведение longjmp() не определено. А мне как раз надо чтобы после следующего вызова выполнение продолжилось с места yield().

Тут возможны какие-то косяки кроме потери локальных переменных (меня это устраивает)? Меня и оригинальный вариант устраивает, но интересно разобраться.

Пример текущего кода https://github.com/speedcontrols/ac_sc_grinder/blob/master/src/calibrator.h#L29-L55

Хочется развернуть так:

static jump_buf YIELD_ENV = NULL;

bool tick() {
    yield_begin();

    while (!wait_knob_dial.tick()) { yield(false); }
    while (!calibrate_static.tick()) { yield(true); }    
    while (!calibrate_speed.tick()) { yield(true); }    
    while (!calibrate_pid.tick()) { yield(true); }    

    yield_end();
    return false;
}

PS. Мне нужна очень простая функциональность, не надо предлагать корутины из буста и прототреды. Не хочу лишние зависимости тащить вместо десятка строк кода.

★★★★★

В спеке сказано, что если функция, вызвавшая setjmp() завершилась, то поведение longjmp() не определено. А мне как раз надо чтобы после следующего вызова выполнение продолжилось с места yield().

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

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

https://code.woboq.org/userspace/glibc/setjmp/longjmp.c.html

Там вроде обычный джамп, не считая масок (которых на эмбедах не будет). В чем может быть проблема если джамп в пределах одной функции? Адрес куда прыгаем не меняется. Структура стека вроде тоже (ну локальные переменные побьются, но ведь я их не использую)

Нашел пример как у меня, только покрасивее:

https://stackoverflow.com/a/30400932

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

если джамп в пределах одной, то проблем не должно быть..тогда это просто goto… потому не понял. где ставится setjump?, это точка куда прыгнут через longjump

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

setjump по канону просто спасает нужные регистры(включая ip), а longjump их восстанавливает(правда в коде есть вроде даже упоминание раскрутки стека, то есть он еще и честно вызывает что-ли деструкторы?).

короче после longjump вы оказываетесь с теми же регистрами, что были при setjump. и как бы в той же точке кода.

alysnix ()

Я ничё не понял, но мне кажется нужен goto

bool tick() {
    yield_true:;

    while (!wait_knob_dial.tick())   { goto yield_false; }
    while (!calibrate_static.tick()) { goto yield_true ; }    
    while (!calibrate_speed.tick())  { goto yield_true ; }    
    while (!calibrate_pid.tick())    { goto yield_true ; }    

    yield_false:;
    return false;
}
LINUX-ORG-RU ()
Последнее исправление: LINUX-ORG-RU (всего исправлений: 1)
Ответ на: комментарий от alysnix

setjump ставится на каждом yield. Может быть внутри блоков while, но все в пределах одной функции.

В самом начале функции макрос, который дергает longjmp если есть сохраненное состояние.

Vit ★★★★★ ()

Чем тебе обычные коллбэки насолили? Или вопрос чисто академический?

pon4ik ★★★★★ ()

А что мешает воспользоваться конечными автоматами и написать

class Tick {
  ...
  bool operator()();
  ...
};
AlexVR ★★★★★ ()

Тред не читал, только заголовок.

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

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

Сорри, не понял что это значит. Я в сях не очень.

https://github.com/speedcontrols/ac_sc_grinder/blob/master/src/calibrator/calibrator_speed.h мне надо вот такое написать нормально. Там муторная последовательность действий с несколькими циклами. На свичах это ужасно.

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

Сорри, не понял что это значит. Я в сях не очень.

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

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

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

Это стандартный кейз, который обычно разруливают тредами или async/await. Но в сишечку ничего приличного не завезли. А тащить многозадачку ради калибровки, которая вызывается 1 раз, мне западло.

Vit ★★★★★ ()

setjmp/longjmp имею семантику escape continuations, т.е. throw/catch, а не полноценных undelimited continuations.

yield же, во всех языках программирования где он есть, реализуется не через call/cc, а через стейт-машину плюс замыкание/объект.

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

Используй коллбэки / указатели на функцию.

Твой бред с лонгджампом потом никто не поймёт.

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

Его не надо понимать. Надо чтобы уровнем выше алгоритм легко читался.

Дай дай пожалуйста ссылку о каких коллбеках и указателях идет речь.

Vit ★★★★★ ()

Мне нужно написать функцию с длинной логикой, которая иногда «примораживается» до следующего тика таймера.

Дёргаешь функцию, которая переводит проц в спящий режим до следующего прерывания. Совершенно непонятно, зачем тут longjmp

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

мне надо вот такое написать нормально

Это уже написано вполне нормально, эффективно, и стандартно для автоматного программирования.

Ну вынеси тела case в отдельные функции, да добавь в комментарий ascii графику переходов состояний для следующих поколений просматривающих код.

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

У нас разные представления о нормальности. Такие вещи на async/await глядят на порядок понятнее и лаконичнее IMO.

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

async-await, внезапно, тоже внутри делается стейт-машиной.

но у тебя Си, а не C#, так что пиши стейт-машину руками, от этого никуда не деться

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

Что конкретно не так с моим примером?

Вот еще пример с ТРЕМЯ вариантами эмуляции возобновляемых функций через макросы https://github.com/zserge/pt/blob/master/pt.h (прототреды, то же что у меня но вид сбоку). На longjump там тоже есть

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

мне надо вот такое написать нормально.

Там сотня строк и пять состояний. Из-за чего весь сыр-бор?

На свичах это ужасно.

Сейчас это легко читается.

LamerOk ★★★★★ ()

для таких целей есть set/get/make/swapcontext() ..

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

Там сотня строк и пять состояний. Из-за чего весь сыр-бор?

Ты внимательнее ВСЮ папочку calibrate/ посмотри. Там намного больше. Из-за 5 стейтов я бы не подрывался.

Сейчас это легко читается.

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

В общем, перепилил вчерне https://github.com/speedcontrols/ac_sc_grinder/commit/14b509c6e304aeac3a1e37d6d1bb31ad90a8a743.

Vit ★★★★★ ()

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

kawaii_neko ★★★ ()

А в C++20 ничего такого не обещают?

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

Обещают какую-то разновидность корутин, но мне надо «прямщаз».

Я выше ссылку на коммит дал. Вроде срослось с макросами. Отковырял от прототредов и переколбасил под себя. Код стал явно человечнее.

Vit ★★★★★ ()

это UB. Нельзя. return address в стеке итд. если и сработант,сломается на других архитектурах

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

А варианты через свичи и указатели на лейблы ок? В имплементациях прототредов делают сразу 3 варианта, типа «красный, синий, голубой, выбирай себе любой».

https://github.com/speedcontrols/ac_sc_grinder/blob/v2/src/calibrator/calibrator_pid.h - вот пример отрефакторенного кода, в мастере старый. По-моему понятнее на порядок.

Vit ★★★★★ ()

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

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

Но в сишечку ничего приличного не завезли.

А может не надо 4.2? Треды в сишечке есть, расчехляй голову и вперёд, солнце ещё высоко.

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

чтобы были треды, нужна ОС, а у топикстартера на его девайсе её скорей всего нет

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

А на девайсе надо пилить стейт машину. Конечные автоматы если по русски.

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

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

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

Почитал. Тут обсуждают UB и goto, что лучше. Это ещё хуже. А сишка - это язык из 70-80 годов.

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

Ты понимаешь, что вместо конструктивной помощи занимаешься доказательством собственной правоты? Нехорошо так делать в техническом треде. Иди в толксы, что ли.

Vit ★★★★★ ()
// #include <stdio.h>
#define F_CPU 4800000

#include <avr/io.h>
#include <util/delay.h>

typedef uint8_t u8;

#define CONCAT_(x, y) x ## y
#define CONCAT(x, y) CONCAT_(x, y)

#define var static u8
#define yield(expr) lastLine = &&CONCAT(a, __LINE__); return (expr); CONCAT(a, __LINE__):

u8 colorGenerator() {
  static void *lastLine = &&a0; goto *lastLine;
  a0:
  for(var i = 0; i < 8; i += 1) {
    yield(i);
  }
  return -1;
}

int main() {
  for(u8 i = 0; i != -1; i = colorGenerator()) {
    PORTB = i;
    //printf("%i", i);
  }
  return 0;
}


Использует фичу gcc https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html

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

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

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