我的项目是用C ++编写的,它利用动态生成的代码将一些东西粘合在一起(使用Fabrice Bellard的 TCC 和一些手动生成的程序集thunk)。动态生成的代码有时会跳转到用C ++实现的“运行时帮助程序”中。
有一个功能允许完全中止动态生成的代码,无论它在哪里,都跳回到C ++(调用者)。为了实现这一点,我只是简单地使用C ++异常:运行时帮助程序(冒充C函数)只是抛出一个C ++异常,它通过生成的函数传播回C ++。我正在使用 SJLJ ,到目前为止一切正常,但我不想依赖于特定的实现(我读到只有SJLJ才安全)。
除了上面的中止方案,我的C ++代码主要在关键情况下使用异常,它不用于通用控制流程。但是,我依靠RAII自动销毁堆栈上的对象。
我的问题是:在理论上和实际上使用longjmp / setjmp是安全的,只要在调用动态生成的函数之前设置setjmp,并且提供longjmp从不通过依赖的C ++函数传播在RAII上(我必须确保在C ++中实现的运行时助手都没有使用它)并且总是落在setjmp中(在函数之前设置)?
或者C ++是如此脆弱,以至于即使这不能保证能够正常工作并且会破坏某些东西?或者,只有抛出实际异常时C ++才会中断?如果异常在本地抛出并立即捕获(在生成的程序集调用的运行时帮助程序中),该怎么办?或者可能只是因为堆栈中有一些外来帧,它会拒绝工作吗?
EG:
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
答案 0 :(得分:1)
在理论上和实际上使用longjmp / setjmp是安全的,只要在调用动态生成的函数之前设置setjmp,并且假设longjmp从不通过依赖于RAII的C ++函数传播(我必须确保没有运行时)用C ++实现的助手使用它)并且总是落在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 ++库的边界并转换为C可以处理的错误代码。