【发布时间】:2013-03-06 02:02:32
【问题描述】:
g++编译器具有零成本异常处理的特性。据我了解,try 什么都不做,但是当抛出异常时,会执行异常处理程序的子例程。像这样:
void foo() {
try {
bar(); // throws.
} catch (Type exc) {
baz();
}
}
在伪代码(c-stylish)中看起来像这样:
void foo() {
bar();
return;
catch1_Type:
baz();
}
bar() 抛出。异常例程执行以下操作:
啊,返回地址在函数 foo() 中!并且返回地址在第一个 try-catch 块中,我们抛出类型 Type,所以异常处理程序的地址是 foo+catch1_Type。所以清理堆栈,这样我们就可以结束了!
现在我的问题是:有没有办法在 C 中实现它? (可以是 C99 或更新版本,虽然我对 gcc 支持的 C 方言感兴趣)。我知道我可以使用例如 libunwind 进行堆栈检查和遍历,尽管我不知道如何获取catch1_Type 标签的地址。这可能是不可能的。
异常处理程序可能是一个不同的函数,这同样可以,但是如何在另一个函数中获取堆栈帧foo 的局部变量的地址?这似乎也是不可能的。
那么……有什么办法吗?我不想用这个进入汇编程序,但如果其他一切都失败了也是可以接受的(尽管局部变量 - 伙计,如果使用不同的优化级别,你永远不知道它们在哪里)。
要明确一点 - 这个问题的目的是避免 setjmp/longjmp 方法。
编辑:我发现了一个很酷的想法,但并不完全奏效:
gcc 中的嵌套函数。他们能做什么?
- 可以访问局部变量,
- 可以在父函数中转到本地标签!
- 可以被我们函数的被调用者调用,前提是我们传递一个指向嵌套函数的指针,因此它可以通过被调用者中的指针获得。
阻碍我做任何零成本的事情的缺点:
- 如果它们未被使用,即使在 -O0 级别也会被优化。我能对此做些什么吗?如果可以的话,我可以在抛出异常时通过符号名称获取地址,它只会完成实现异常的工作,而这些异常在不抛出时不会花费任何成本......
【问题讨论】:
-
这似乎对这个问题略有不同:stackoverflow.com/questions/307610/… ...它询问异常在 C++ 中是如何工作的;一旦您知道 C++ 如何实现异常,您就可以决定是否可以为您的 C 程序复制该实现。
-
@MartinAtkins,我只是稍微了解 g++ 中的异常是如何实现的,尽管我认为我在某处读到 g++ 中的异常支持部分在编译器和链接器方面,所以重复如果不修改工具,它在 C 中将是一个问题。不过,我会阅读您提到的主题以获得更多法力值。
-
你完全错了,
try/catch构造是有代价的。首先try必须以某种方式标记堆栈和所有寄存器的位置,而catch/throw必须逐个展开try上下文。 Csetjmp/longjmp中有一个“等效”功能,它的作用完全相同,只是它周围的胶水更少。 -
@JensGustedt,不,我没记错。展开表是在构建时生成的(我认为是链接时间?),它们足以让异常例程展开堆栈并找到异常处理程序的地址。在运行时,不会发生任何标记。 systemcall.org/blog/2010/10/zero-cost-exception-handling-in-cpp
-
那篇文章至少误导了
setjmp/longjmp部分。以 C 为中心的方法永远不必通过跳转表的“列表”。
标签: c exception optimization elf