Мой проект написан на C ++, и он использует динамически сгенерированный код, чтобы склеить некоторые вещи (используя Fabrice Bellard’s TCC и немного сгенерированных вручную сборочных сборок). Динамически генерируемый код иногда переходит в «помощников времени выполнения», реализованных в C ++ и обратно.
Есть функция, которая позволяет вообще прервать динамически сгенерированный код, где бы он ни находился, возвращаясь к C ++ (вызывающему). Чтобы достичь этого, я просто использую исключения C ++: помощник во время выполнения (выдавая себя за функцию C) просто генерирует исключение C ++ и распространяется через сгенерированные функции обратно в C ++. я использую SJLJ и пока все работает нормально, но я не хочу зависеть от конкретной реализации (я читал, что это безопасно только с SJLJ).
Помимо схемы прерывания, описанной выше, мой код C ++ использует исключения, в основном в критических ситуациях, он не используется для общего потока управления. Тем не менее, я полагаюсь на RAII для автоматического уничтожения объектов в стеке.
Мой вопрос: Теоретически и практически безопасно использовать longjmp / setjmp вместо этого, при условии, что setjmp установлен прямо перед вызовом динамически генерируемой функции, и при условии, что longjmp никогда не распространяется через функции C ++, которые полагаются на RAII (я должен убедиться, что ни один из помощников времени выполнения, реализованных в С ++ использует это) и всегда попадает в setjmp (устанавливается прямо перед функцией)?
Или C ++ настолько хрупок, что даже это не гарантированно будет работать хорошо и что-то испортит? Или, может быть, C ++ обрывается, только если генерируются реальные исключения? Что если исключения генерируются локально и сразу же перехватываются (в помощниках времени выполнения, вызываемых сгенерированной сборкой), это безопасно? Или, может быть, просто потому, что в стеке есть несколько посторонних кадров, он откажется работать?
НАПРИМЕР:
jmp_buf buf; // thread-local
char* msg; // thread-local
// ... some C++ code here, potentially some RAII thingy
GeneratedFunc func = (GeneratedFunc)compile_stuff();
if (!setjmp(buf)) {
// somewhere deep inside, it calls longjmp to jump back to the top function in case a problem happens
func();
} else {
printf("error: %s\n", msg);
// do something about the error here
}
// some other C++ code
Теоретически и практически безопасно использовать longjmp / setjmp вместо этого, при условии, что setjmp установлен прямо перед вызовом динамически генерируемой функции, и при условии, что longjmp никогда не распространяется через функции C ++, которые полагаются на RAII (я должен убедиться, что ни один из помощников времени выполнения, реализованных в С ++ использует это) и всегда попадает в setjmp (устанавливается прямо перед функцией)?
Стандарт 18.10 / 4 гласит:
…
longjmp(jmp_buf jbuf, int val)
имеет более ограниченное поведение в этом международном стандарте.setjmp
/longjmp
Пара вызовов имеет неопределенное поведение при заменеsetjmp
а такжеlongjmp
отcatch
а такжеthrow
будет вызывать любые нетривиальные деструкторы для любых автоматических объектов.
Таким образом, это не просто RAII, а любой размещенный в стеке объект, возражающий против нетривиальных деструкторов (то есть «ресурсы» могут быть получены после строительство, но все еще должно быть освобождено во время уничтожения, или могут быть побочные эффекты разрушения, кроме освобождения ресурса, такие как регистрация).
Или C ++ настолько хрупок, что даже это не гарантированно будет работать хорошо и что-то испортит?
Это должно работать с учетом оговорки выше о тривиальных деструкторах (что является довольно большим ограничением).
Или, может быть, C ++ обрывается, только если генерируются реальные исключения? Что если исключения генерируются локально и сразу же перехватываются (в помощниках времени выполнения, вызываемых сгенерированной сборкой), это безопасно?
Это не связано с setjmp
/ longjmp
поведение. Если вы бросаете и ловите внутри нормального кода, сгенерированного компилятором C ++, не должно быть никаких остаточных / последующих проблем с выполнением (повторным) ввода динамически сгенерированного кода. Тот же подход используется для обернутых библиотек C ++, вызываемых в / из C; исключения могут быть пойманы на границах библиотеки C ++ и преобразованы в коды ошибок, которые C может обрабатывать.