Jump to content
Fraser

exceptions and static storage duration objects

Recommended Posts

If I have an object with static storage duration that throws an exception while constructing it is not caught by the expected catch statement.  Instead there is an external exception EEFACE and 'Abnormal program termination' is reported.  This only happens with a VCL application.  The code would work otherwise.  I have to have my object declared static in a function that returns it by reference in order that its construction will be delayed until after something clearly VCL related.  This must be something that is known about.  I am not providing example code because the requirements for this are described adequately.  BTW this is not the undeterminable order of construction of objects of static storage duration problem.

Share this post


Link to post
15 minutes ago, Fraser said:

An object is declared in a cpp file at namespace scope.

That is not what you described originally:

Quote

I have to have my object declared static in a function

Two different things.  So, which one are you actually working with? Can you provide a code example that is crashing for you?

Share this post


Link to post

What appears to be required is a VCL project with an object declared at global scope that throws during construction and an appropriate catch statement in the constructor that will not catch the exception.

Share this post


Link to post

I've tried that and the bug has not materialised.  Probably it requires a large project to appear.

Share this post


Link to post
44 minutes ago, Fraser said:

What appears to be required is a VCL project with an object declared at global scope that throws during construction and an appropriate catch statement in the constructor that will not catch the exception.

Without an example showing the error in action, it is hard to diagnose this.

Share this post


Link to post

Are there any open source projects for C++ Builder that could be used for this kind of problem?

Share this post


Link to post
On 8/8/2022 at 11:43 AM, Fraser said:

Are there any open source projects for C++ Builder that could be used for this kind of problem?

A strange question to ask. You are the person who can generate the fault you are trying to describe. So only you can really judge if the same problem exists when the correct set of circumstances are made to exist in another (eg open source) project....

Share this post


Link to post

Can you give a brief sample code of the kind of thing that you think fails sometimes, even though it "works" in your brief sample. This could well be an example of doing something that C++ is allowed to use the "undefined behaviour" get out (undefined behaviour often tends to one of either  "a complete crash" or "sometimes works and sometimes doesn't"

Share this post


Link to post


#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;

class A {
    A() noexcept try {
    throw std::logic_error("test");
    }
    catch (std::exception const &ex) {
        std::cout << "Caught an STL exception" << std::endl;
        }
    };

A a;

A & AccessA() {
    static A obj;
    return obj;
    }

 

//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}
//---------------------------------------------------------------------------

 

The throw is placed there as a deliberate test.  The object a throws an exception and it gets caught by the handler but the CPU view shows that a function called clang terminate is called.  The object obj throws an exception and it gets caught by the handler and the correct code is executed.

Share this post


Link to post

For obj the initial message is Project X.exe raised exception class $std@logic_error with message 'Exception Object Address:0x2EAAE98'.

For a the initial message is similar.  Next there is Project X.exe raised exception class EExternalException with message 'Extrenal Exception EEFACE'.  Next there is another before 'Abnormal program termination.'

Share this post


Link to post
1 hour ago, Fraser said:

class A {
    A() noexcept try {
    throw std::logic_error("test");
    }
    catch (std::exception const &ex) {
        std::cout << "Caught an STL exception" << std::endl;
        }
    };

I would not have expected this code to compile, but if it does then it has undefined behavior.

 

You are using a function-try block around the constructor body. Even though you are catching an exception, a function-try implicitly re-throws every exception it catches, if you don't explicitly re-throw it yourself.  But you marked the constructor as noexcept, promising it will never throw an exception.  You broke the promise.

 

At the very least, you need to get rid of the noexcept to get rid of the undefined behavior.  You will still crash at startup, but that is because of another issue...

Quote

The throw is placed there as a deliberate test.  The object a throws an exception and it gets caught by the handler but the CPU view shows that a function called clang terminate is called.

Because the thrown exception is not actually being fully caught.  You are constructing an A object in global scope before Winmain() is called, you are not waiting for AccessA() to be called first.  That global object is throwing an exception that is not being caught, thus aborting the process startup.  A thrown exception that is not caught by the throwing thread causes std::terminate() to be called, which ends the calling process.  Likewise, if an exception escapes a noexcept function without being caught, std::terminate() is called as well.

Quote

The object obj throws an exception and it gets caught by the handler and the correct code is executed.

But the exception is not being swallowed by the catch handler, it is being re-thrown.

Edited by Remy Lebeau

Share this post


Link to post
Quote

I would not have expected this code to compile, but if it does then it has undefined behavior.

It is not my function, it's a mock-up.  My function calls exit so it does not throw again and can be declared noexcept.

Quote

You are constructing an A object in global scope before Winmain() is called, you are not waiting for AccessA() to be called first.

I don't know what you are saying.  a and AccessA() are diferent implementations that I am trying out and never used together.

Quote

That global object is throwing an exception that is not being caught, thus aborting the process startup.

It has a handler so why wouldn't it be caught?  It is caught when constructing obj.

Quote

But the exception is not being swallowed by the catch handler, it is being re-thrown.

For obj's exception it is caught and the handler executes correctly and there is not another throw.

Share this post


Link to post
4 hours ago, Fraser said:

It is not my function, it's a mock-up.  My function calls exit so it does not throw again and can be declared noexcept.

There is no call to exit() in the code you presented.

Quote

I don't know what you are saying.  a and AccessA() are diferent implementations that I am trying out and never used together.

In the code you presented, you have 2 separate object instances of the A class:

A a; // <-- here

A & AccessA() {
    static A obj; // <-- here
    return obj;
}

The 1st object is being created in global scope during program startup, and the 2nd object is being created in static memory the first time AccessA() is called at runtime.  The global object is created before your project's WinMain() function is executed  The code presented is not calling AccessA() at all, so the compiler/linker is free to optimize it away, if it wants to.

Quote

It has a handler so why wouldn't it be caught?  It is caught when constructing obj.

I already explained that in detail in my previous reply.  Go re-read it again more carefully.  Do you understand the difference between a function-try block and a normal non-function-level try block?

Quote

For obj's exception it is caught and the handler executes correctly and there is not another throw.

No, there is no explicit re-throw statement in the catch block.  But the catch block is performing an implicit re-throw, because you are using a function-try block instead of a normal try block:

Quote

Every catch-clause in the function-try-block for a constructor must terminate by throwing an exception. If the control reaches the end of such handler, the current exception is automatically rethrown as if by throw;. The return statement is not allowed in any catch clause of a constructor's function-try-block.

If you really want to swallow the exception, use a normal try block instead of a function-try block, eg:

class A {
	A() noexcept {
		try {
			throw std::logic_error("test");
		}
		catch (std::exception const &ex) {
			std::cout << "Caught an STL exception" << std::endl;
		}
	}
}; 

Notice the try-catch is now inside the constructor's body, rather than around the body as in your original example.

Edited by Remy Lebeau
  • Like 1

Share this post


Link to post
Quote

For obj's exception it is caught and the handler executes correctly and there is not another throw.

Quote

No, there is no explicit re-throw statement in the catch block.  But the catch block is performing an implicit re-throw, because you are using a function-try block instead of a normal try block:

More than once I have said the AccessA() type of implementation works as expected and you say otherwise.  Thats ridiculous.

 

You are the only person that has said anything about rethowing.  I have seen nothing.  For a start an object of one type, in my case std::logic_error, would be rethrown as the same object.  But the next exception I witness, for object a, has type EExternalException.

 

I'm not aware of any difference between function-try-block and try-block other than a function-try-block is a like a try-block around a functions entire body, a shorter notation for that.  Even the standard states that where it says "try block" it can apply to either.

 

Here is the rough mock-up of my program with some corrections;

 

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"

 

#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;

 

class A {
    A() noexcept try {
    throw std::logic_error("test");
    }
    catch (std::exception const &ex) {
        std::cout << "Caught an STL exception" << std::endl;

        std::exit("Done");
        }
    };

 

A a;

 

// Alternative implementation that works as expected;

/*A & AccessA() {
    static A obj;
    return obj;
    }*/

 

__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}

Share this post


Link to post
7 hours ago, Fraser said:

More than once I have said the AccessA() type of implementation works as expected and you say otherwise.  Thats ridiculous.

I'm just going off of the actual code you posted earlier.

Quote

For a start an object of one type, in my case std::logic_error, would be rethrown as the same object.  But the next exception I witness, for object a, has type EExternalException.

That tends to happen when the runtime/debugger can't interpret the actual exception object at the level it was detected at, basically treating it as an "unknown" exception type.

Quote

I'm not aware of any difference between function-try-block and try-block other than a function-try-block is a like a try-block around a functions entire body, a shorter notation for that.  Even the standard states that where it says "try block" it can apply to either.

Did you not see the quote I posted earlier?

https://en.cppreference.com/w/cpp/language/function-try-block

Quote

Every catch-clause in the function-try-block for a constructor must terminate by throwing an exception. If the control reaches the end of such handler, the current exception is automatically rethrown as if by throw;. The return statement is not allowed in any catch clause of a constructor's function-try-block.

Quote

Here is the rough mock-up of my program with some corrections;

Minor nitpicks: the A() constructor is not public, and std::exit() takes an exit code integer instead of a string as input.

 

In any case, when I test the code outside of C++Builder (which I don't have installed at the moment), the code works fine when using std::exit() in the function-try's catch handler:

 

https://onlinegdb.com/WqiBtg8dD

https://onlinegdb.com/v7B8acNo4

 

And terminates the process with a re-thrown exception when not using std::exit() in the handler:

 

https://onlinegdb.com/q0KLc6Cne

https://onlinegdb.com/rHqvw5kmo

Edited by Remy Lebeau

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×