{"id":111360,"date":"2025-07-09T07:00:00","date_gmt":"2025-07-09T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=111360"},"modified":"2025-07-09T13:38:54","modified_gmt":"2025-07-09T20:38:54","slug":"20250709-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20250709-00\/?p=111360","title":{"rendered":"When I install an unhandled structured exception filter, why doesn&#8217;t <CODE>std::<WBR>terminate<\/CODE> get called?"},"content":{"rendered":"<p>For diagnostic and reliability purposes, a customer wanted to detect and report all all unhandled C++ exceptions as well as all unhandled structured exceptions. Their idea was to cover both exits:<\/p>\n<ul>\n<li>Use <code>set_terminate<\/code> to install a <code>std::terminate<\/code> handler, which is called when a C++ exception goes unhandled.<\/li>\n<li>Use <code>Set\u00adUnhandled\u00adException\u00adFilter<\/code> to install an unhandled exception handler, which is called when a structured exception goes unhandled.<\/li>\n<\/ul>\n<p>When they did this, they found that C++ exceptions never reached their <code>std::<wbr \/>terminate<\/code> handler. They instead went to the unhandled structured exception filter.<\/p>\n<p>The customer understood that C++ exceptions are internally implemented by the Microsoft Visual C++ compiler in terms of structured exceptions but their understanding was that the unhandled structured exception filter is called last, after all other exception filters. So the C++ unhandled exception filter should have run first.<\/p>\n<p>While it&#8217;s true that the unhandled structured exception filter is called last, what the customer didn&#8217;t realize is that the way that the Microsoft Visual C++ compiler recognizes unhandled exceptions is by itself installing an unhandled exception filter!<\/p>\n<p>To throw a C++ exception, the Microsoft Visual C++ compiler calls <code>Raise\u00adException<\/code>, and the operating system then walks up the stacks looking for any functions that have installed structured exception filters. The C++ compiler generates these structured exception filters for any function that requires awareness of exceptions, even if they don&#8217;t themselves catch exceptions. For example, if there are local variables with destructors, the structured exception filter will destruct those local variables when an exception escapes their frame. (This is, after all, the principle behind RAII.)<\/p>\n<p>If no functions on the stack handle the structured exception (which itself represents the C++ exception), then the custom unhandled structured exception filter installed by the Microsoft Visual C++ compiler inspects the exception, realizes that it&#8217;s a C++ exception, and concludes that what it has is an unhandled C++ exception.<\/p>\n<p>Therefore, if you install your own custom unhandled structured exception filter, it will replace the unhandled structured exception filter installed by the Microsoft Visual C++ compiler, and therefore you will see the unhandled C++ exception instead of the compiler infrastructure.<\/p>\n<p>Now that we understand what is happening, we can look for solutions next time.<\/p>\n<p><b>Bonus chatter<\/b>: If you think about it, the Visual C++ runtime doesn&#8217;t have much choice but to install an unhandled structured exception filter. If a C++ exception goes unhandled, that means that the corresponding structured exception goes unhandled, and how else are you going to realize that this has happened aside from installing your own unhandled structured exception filter?<\/p>\n<p>Okay, so one way to solve this would be to have the C++ runtime raw entry point install an exception handler filter. Since it is the outermost exception handler filter on the thread, it will get called for anything that escaped <code>main<\/code> unhandled. Similarly, the threads created by <code>_beginthreadex<\/code> and <code>std::thread<\/code> could install their own exception handler filter before calling the app-supplied thread function. However, this fails to observe unhandled exceptions that escape threads that were created by calling <code>Create\u00adThread<\/code> directly, so you would have to add another rule that says &#8220;Threads created by <code>Create\u00adThread<\/code> must not allow exceptions to escape.&#8221; Since functions generally do not know how the thread they are running on was created, this rule breaks down quickly.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>You&#8217;re using the same hook that the compiler uses to call <CODE>std::<WBR>terminate<\/CODE>.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-111360","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>You&#8217;re using the same hook that the compiler uses to call <CODE>std::<WBR>terminate<\/CODE>.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111360","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=111360"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111360\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=111360"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=111360"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=111360"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}