如何在运行时确定特定 C++ 是否存在 catch 块?异常类?
在 Linux 上,我希望能够确定特定异常类(或该类的超类)的 catch 块当前是否在范围内(忽略 catch all 块)。
特别是,我希望能够实现 isThereACatchBlock 函数,其行为如下:
bool isThereACatchBlock( std::type_info const & ti ) {
...;
}
class MyException {
};
class MyDerivedException : public MyException {
};
class MyOtherException {
};
void f() {
try {
isThereACatchBlock( typeid( MyException ) ); // Should return true
isThereACatchBlock( typeid( MyDerivedException ) ); // Should return true
isThereACatchBlock( typeid( MyOtherException ) ); // Should return false
} catch( MyException const & e ) {
} catch( ... ) {
}
}
我知道系统具有此信息,以便它可以正确地实现异常处理 - 我相信它已存储在 .eh_frame 和/或 .gcc_ except_table 部分中,如 这篇文章。但是,我不确定程序是否有任何简单的方法来解释该信息。有人可以帮忙吗?
On Linux, I would like to be able to determine whether a catch block for a particular class of exception (or a superclass of that class) is currently in scope (ignoring catch all blocks).
In particular, I would like to be able to implement the isThereACatchBlock
function to behave as follows:
bool isThereACatchBlock( std::type_info const & ti ) {
...;
}
class MyException {
};
class MyDerivedException : public MyException {
};
class MyOtherException {
};
void f() {
try {
isThereACatchBlock( typeid( MyException ) ); // Should return true
isThereACatchBlock( typeid( MyDerivedException ) ); // Should return true
isThereACatchBlock( typeid( MyOtherException ) ); // Should return false
} catch( MyException const & e ) {
} catch( ... ) {
}
}
I know that the system has this information, in order that it can implement exception handling correctly -- I believe it's stored in the .eh_frame and/or .gcc_except_table sections, as described in this post. However, I'm not sure if there is any easy(-ish) way for a program to interpret that information. Can anyone help?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
阅读您的评论之一,我发现您想要这样做的原因之一是避免为已处理的异常生成回溯,但对于未处理的异常,您希望拥有回溯。
如果这就是您想要执行此操作的原因,您可以使用 std::set_terminate() 来设置终止处理程序,该处理程序会在发生未处理的异常时调用。使用我自己的回溯处理程序进行测试,回溯显示一直到导致失败的 throw() 的跟踪,因为 throw 基本上在意识到不会捕获异常后直接调用终止处理程序。
请注意,这仅捕获最近一次抛出的堆栈,直到其终止。如果捕获然后重新抛出异常,则初始抛出和捕获之间的堆栈将被展开并且不再可用。
Reading one of your comments, I saw that one reason you want to do this is to avoid generating a backtrace for handled exceptions, but unhandled exceptions, you want to have the backtrace.
If that's why you want to do this, you can use
std::set_terminate()
to set up a termination handler, which is called when an unhandled exception occurs. Testing with my own backtrace handler, the backtrace shows trace all the way up to the throw() that caused the failure, since the throw basically calls the terminate handler directly, after it realizes the exception is not going to be caught.Note, this only captures the stack from the most recent throw until it's terminated. If you catch and then rethrow the exception, the stack between the initial throw and the catch is unwound and no longer available.
您可以通过创建一个“金丝雀”过程来测试发生的情况并报告结果,以一种非常笨拙的方式解决这个问题。我整理了一个“概念验证”示例:
它的优点是半便携式。我会在愤怒时使用它吗?可能不会。我最担心的是,一些异常可能会产生显着的副作用,这些异常会做出奇怪而美妙的(但意想不到的)事情。例如,删除文件或更糟的析构函数。您还需要确保您测试的异常是默认可构造的,而不是原始类型。我的示例的另一个问题是线程安全,不仅仅是 TestException 的琐碎的
fd
静态成员 - 您可能需要在金丝雀进程运行时暂停任何其他线程。免责声明:这样做可能是一个坏主意。
You can solve this in a very kludgey kind of way by making a "canary" process that tests what happens and reports the result. I put together a "proof of concept" example:
It has the advantage that it's semi-portable. Would I use it in anger though? Probably not. My big concern would be that there might be significant side-effects from some of the exceptions that do weird and wonderful (but unexpected) things. E.g. a destructor which deletes a file or worse. You'll also need to ensure the exceptions you test are default constructible and not primitive types. The other problem with my example is thread safety, more than just the trivial
fd
static member ofTestException
- you probably need to make any other threads suspend whilst the canary process is running.Disclaimer: Doing this is probably a bad idea.
我的想法是这样的。请记住,我正在即时编写所有代码,因此它可能并不完美。 :)
实际上用代码解释可能更容易,但我会先尝试给出要点。由于您对 catch (...) 不感兴趣,因此我没有专注于检测这一点,但是,我认为修改想法来处理该问题也相对容易。 (注意:最初我打算使用指向函数的指针来告诉你所在的函数,但我最终选择了这个名称,因为我没有考虑虚拟函数。我确信这可以如果有必要,所有这些都可以优化。)
创建以下内容:
void 指针字符串来保存 在安装程序中创建的函数的名称:
try
之前,放置一个 catch 类型结构,其中包含要捕获的类型名称以及该函数中捕获的所有异常,以及堆栈上的指向函数名称的指针。"..."
对于默认捕获来说很好)。typeid
获取未修饰的类型名称,然后使用.raw_name()
获取损坏的名称拆解来说毫无意义:
catch
块中,撕掉 在函数中的最后一个catch
块之后,将堆栈向下超出您要捕获的函数的类型主要问题这种解决方案的缺点是它显然非常繁琐。
一种解决方案是[打赌你看到了这个即将到来的]宏。
ExceptionStackHandler.h
然后在你的代码中它看起来像
现在,我承认,它很丑陋。真的很丑。我确信有更好的方法来编写这些宏,但作为一个理论上的概念验证,我认为没有什么是行不通的。但真正的解决方案不可能这么难。结果不佳的原因并不是这个想法那么丑陋。只是宏无法以干净的方式实现它。由于它是一种常规模式,因此应该有一种方法可以使其发生,甚至无需触及源代码。如果 C 预处理器不是唯一的选择就好了……
;) 那么。其实我觉得可能有。一个更好的解决方案是使用更强大的预处理器,它允许您在不进行额外预处理(例如指令作为注释)的情况下进行编译,从而提供更清晰的 C++ 代码。我认为使用 CS-Script (将在 Mono 下运行),我相信“预编译器”过程的文档中包含一些示例,可以让您执行此操作。实际上,为此:您甚至不需要指令。指令会很酷,但是您不需要通用宏处理器来完成您需要的操作。当然,不言而喻,您可以将其写入任何能够处理文本文件的内容中。
尽管我还没有尝试实现它,但我认为这可能只是一个在整组文件上运行的处理器,不需要直接对代码进行任何修改。 (找到文件中的所有
try/catch
块,收集类型,创建额外的语句,然后写出文件。)也许移动 Makefile 从中提取构建文件的目录,然后移动到编译,处理所有文件并将输出放入新的构建子目录中。我打赌 LINQ 可以通过一些小语句来完成它,尽管这并不意味着我可以编写 LINQ。 :) 我仍然打赌这不会是一项艰巨的任务,而且这将是实现该解决方案的完美方式;定义堆栈、类以及类中的静态检查器函数。这让我想起......“完整性”:
所以,最后:我很难想象处理像您最终使用宏的代码这样的代码。现在,我没有缩进,我想这样它会变得半可读,但问题刚刚发生:现在,如果您要处理七个异常类型,则有七个缩进将所有内容推离屏幕。但我确实认为可以通过一个简单的外部应用程序来完成一些事情,它会自动为您完成这一切。
My thought is this as follows. Please keep in mind I am writing all code for this on the fly so it may not be perfect. :)
It's actually probably easier to explain in code, but I'll try to give the gist first. Since you are not interested in
catch (...)
I have not focused on detecting that, however, I think it would be relatively easy to modify the idea to handle that as well. (Note: originally I was going to use a pointer to the function as the way to tell which function you are in, but I have ended up going with the name because I wasn't thinking about virtual functions. I'm sure this can all be optimized if necessary though.)Create the following:
void pointerstring to hold the name of the function it was created inSetup:
try
, place a catch type struct with the type name being caught as well as with all exceptions caught in this function on the stack, along witha pointer tothe name of the function."..."
is fine for default catches).typeid
to get the undecorated type names, then using.raw_name()
to get the mangled nameTeardown:
catch
block, at the top, tear the stack down one beyond the type you are catching for the function you are incatch
blocks in a function, tear the stack down one beyond the first instance of the teardown in the last catchThe main issue with this solution is that it is clearly very cumbersome.
One solution is [bet you saw this coming] macros.
ExceptionStackHandler.h
Then in your code it would look like
Now, I admit, it's ugly. Reeeal ugly. I'm sure there's a better way to write those macros, but as just a theoretical proof-of-concept I don't think there's anything there that doesn't work. But the real solution can't be this hard. The reason it doesn't turn out well isn't that the idea is that ugly. It's just that the macros can't implement it in a clean way. Since it's such a regular pattern there just oughtta be a way to make it happen without even touching your source code. If only the C preprocessor wasn't the only choice...
;) So. Actually I think there may be. A superior solution is to use a more-powerful preprocessor, that gives cleaner C++ code by allowing you to compile even without being additionally preprocessed (e.g. directives as comments). I think that it would be fairly easy to write something up using a tool like CS-Script (which will run under Mono) and I believe some examples are included in the documentation of the 'precompiler' process which lets you do this. And, really, for this: you don't even need directives. Directives would be cool, but you don't need a general purpose macro processor to do what you need. Of course, it goes without saying that you could write it in anything that has the capability to process a text file.
Although I have not tried to implement it yet, I think this could perhaps be just a processor that is run on an entire group of files that require no modifications directly to the code whatsoever. (Find all
try/catch
blocks in the file, gather up the types, create the extra statements, and write out the file.) Perhaps move the directory the Makefile pulls the build files from, then prior to compiling, process all the files and put the output in the new build subdirectory. I bet LINQ could do it in a few little statements, although that doesn't mean I can write the LINQ. :) I still bet it wouldn't be that big of a task, amd it would be the perfect way to implement the solution; define the stack, the class, and the static checker functions in the class.Which reminds me... for "completeness":
So, in closing: I can hardly imagine dealing with code like the code that you end up having with the macros. Now, I didn't indent, and I suppose with that it would become semi-more semi-readable, but the problem has just moved: now if you have seven exception types being handled, you have seven indents pushing everything off the screen. But I do think something can be done with an simple external application that does it all automatically for you.
我不知道如何检查存在哪些 catch 块,这可能需要付出巨大的努力,并且如果您更改同一编译器的较小版本,则可能会中断。您的评论表明您真正想要的是在异常的构造函数中获取堆栈跟踪,而不是捕获异常的位置。
实际上,在 linux/gcc 中使用 backtrace 和 backtrace_symbols 函数很容易做到这一点,GCC 很乐意为您提供堆栈转储。另请参阅:手册页和这个问题(注意这问题是关于崩溃的,但是无论程序是否崩溃,您都可以随时执行此操作)。
即使异常被代码的某些部分捕获(...或其他),这仍然会生成堆栈转储,但它会让代码继续运行而不是调用 abort() 或 Terminate()。但是您可以查看日志以查找导致问题的原因,您不应该有那么多(如果这样做,您可能错误地使用了异常......它们是 if/else/while 或 returned 的糟糕替代品有时会出现错误代码),
I don't know how you could check what catch blocks exist, it would probably take significant effort and would likely break if you changed even minor versions of the same compiler. Your comment indicates what you really want is to get stack traces in the constructors of your exceptions, rather than the location they are caught.
That is actually easy to do in linux/gcc with the
backtrace
andbacktrace_symbols
functions GCC will happily give you a stack dump. See also: the man page and this SO question (note the question is about crashes, but you can do this at any time in your program whether it is crashing or not).This will still generate stack dumps even if the exception was caught by some piece of your code (... or otherwise), but it will let the code continue running rather than calling abort() or terminate(). But you can look through the logs to find which one caused your problem, you shouldn't have that many (if you do, you're probably using exceptions wrong... they are a poor substitute for if/else/while or returning an error code sometimes),
“我知道系统有这些信息,以便它能够正确地实现异常处理”
这是不正确的。系统可以使用真正的堆栈作为异常处理程序,即只能访问顶部异常处理程序的堆栈。在 C++ 中,在决定是否输入最上面的异常处理程序之前,您永远不需要知道是否存在另一个异常处理程序。抛出的异常要么由顶级处理程序处理,然后您使用它,要么它不被处理,您弹出未使用的顶级异常处理程序。
因此,在您的情况下,运行时可能可见的唯一异常处理程序是
catch( MyException )
。这意味着您无法知道isThereACatchBlock( typeid( MyOtherException ) );
应该为false
。访问catch( MyException )
“后面”的异常处理程序的唯一方法可能是抛出
由catch( MyException )
未处理的异常。"I know that the system has this information, in order that it can implement exception handling correctly"
That's untrue. A system may use a true stack for exception handlers, i.e. a stack where you can only access the top exception handler. In C++ as it stands, you never need to know whether another exception handler exists before deciding whether the top one is entered. Either the exception thrown is handled by the top handler, and you use it, ot it's not handled and you pop the top exception handler unused.
In your case, the only exception handler that might be visible to the runtime is therefore
catch( MyException )
. That means you cannot know thatisThereACatchBlock( typeid( MyOtherException ) );
should befalse
. The only way to access the exception handler "behind"catch( MyException )
might be tothrow
an exception unhandled bycatch( MyException )
.你可以玩重新抛出...
}
You can play with the rethrow...
}