throwing exception under low memory conditions

297 views
Skip to first unread message

Michael Kilburn

unread,
Aug 15, 2017, 3:33:08 AM8/15/17
to ISO C++ Standard - Discussion
Hi,

I have discovered recently that "throw X(...);" terminates application if it can't allocate memory for exception being thrown. This happens with GCC. Standard does not specify what should happen in this situation (see C++11 15.1.p4).

I am trying to discuss this situation with someone who is closer to committee than me... Is it a defect? Standard does not specify behavior here and implementations are free to terminate (and they do) -- problem is that this makes impossibly to write a C++ program that survives out-of-memory situation -- even throwing std::bad_alloc can terminate your app.

If it isn't a defect -- how to write such program?

I've investigated GCC and apparently it can even hang your application in this case (deadlock) if you are unlucky enough. If you are interested, details are here:

If I am wrong somewhere -- I'd appreciate if my mistake is pointed out.

Thank you,
Michael.

Chris Hallock

unread,
Aug 15, 2017, 8:18:16 AM8/15/17
to ISO C++ Standard - Discussion
On Tuesday, August 15, 2017 at 3:33:08 AM UTC-4, Michael Kilburn wrote:
I have discovered recently that "throw X(...);" terminates application if it can't allocate memory for exception being thrown. This happens with GCC. Standard does not specify what should happen in this situation (see C++11 15.1.p4).

I am trying to discuss this situation with someone who is closer to committee than me... Is it a defect? Standard does not specify behavior here and implementations are free to terminate (and they do) -- problem is that this makes impossibly to write a C++ program that survives out-of-memory situation -- even throwing std::bad_alloc can terminate your app.

This is an implementation-internal memory allocation. What could the Standard possibly say?

Michael Kilburn

unread,
Aug 15, 2017, 11:18:55 AM8/15/17
to ISO C++ Standard - Discussion
A lot of different things... For example:
"This allocation failure should result in std::bad_alloc being thrown. Implementation should never fail to throw std::bad_alloc"

it certainly may come as a surprise to those who use exception specification, but at least I will be able to write application that doesn't mysteriously die when it runs out of memory

Thiago Macieira

unread,
Aug 15, 2017, 11:28:08 AM8/15/17
to std-dis...@isocpp.org
On Tuesday, 15 August 2017 08:18:54 PDT Michael Kilburn wrote:
> A lot of different things... For example:
> "This allocation failure should result in std::bad_alloc being thrown.
> Implementation should never fail to throw std::bad_alloc"
>
> it certainly may come as a surprise to those who use exception
> specification, but at least I will be able to write application that
> doesn't mysteriously die when it runs out of memory

I thought libstdc++ already had an emergency buffer for throwing
std::bad_alloc in OOM conditions. I don't know if that is something that needs
to be opted in, though. Please see its documentation.

Requiring that the buffer always be allocated could be unnecessary overhead
and violation of C++'s principle of "don't pay for what you don't use".

In any case, you're running on an RTOS of your own, not a generic OS. You
should be able to modify the C++ support library's build to enable the buffer
allocation by default.

--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center

Nicol Bolas

unread,
Aug 15, 2017, 11:28:28 AM8/15/17
to ISO C++ Standard - Discussion
On Tuesday, August 15, 2017 at 11:18:55 AM UTC-4, Michael Kilburn wrote:
On Tuesday, August 15, 2017 at 7:18:16 AM UTC-5, Chris Hallock wrote:
On Tuesday, August 15, 2017 at 3:33:08 AM UTC-4, Michael Kilburn wrote:
I have discovered recently that "throw X(...);" terminates application if it can't allocate memory for exception being thrown. This happens with GCC. Standard does not specify what should happen in this situation (see C++11 15.1.p4).

I am trying to discuss this situation with someone who is closer to committee than me... Is it a defect? Standard does not specify behavior here and implementations are free to terminate (and they do) -- problem is that this makes impossibly to write a C++ program that survives out-of-memory situation -- even throwing std::bad_alloc can terminate your app.

This is an implementation-internal memory allocation. What could the Standard possibly say?

A lot of different things... For example:
"This allocation failure should result in std::bad_alloc being thrown. Implementation should never fail to throw std::bad_alloc"

Well that would change nothing, since "should" only specifies a recommendation ;) The word you're looking for is "must".

That being said, I don't think I would consider this a "defect" in the standard. It seems likely that this is deliberately left as an implementation detail. Basically, it's a quality-of-implementation issue; an implementation can provide a guarantee that all standard-library-generated `std::bad_alloc`s will be able to be generated. But they aren't required to do so.

it certainly may come as a surprise to those who use exception specification, but at least I will be able to write application that doesn't mysteriously die when it runs out of memory

Unless it runs out of memory due to a stack allocation. In which case, you're in undefined behavior land. At least if an exception cannot be allocated, `std::terminate` will be called. With a stack allocation OOM error, you won't even get that.

Michael Kilburn

unread,
Aug 15, 2017, 11:43:37 AM8/15/17
to ISO C++ Standard - Discussion


On Tuesday, August 15, 2017 at 10:28:28 AM UTC-5, Nicol Bolas wrote:
On Tuesday, August 15, 2017 at 11:18:55 AM UTC-4, Michael Kilburn wrote:
On Tuesday, August 15, 2017 at 7:18:16 AM UTC-5, Chris Hallock wrote:
On Tuesday, August 15, 2017 at 3:33:08 AM UTC-4, Michael Kilburn wrote:
I have discovered recently that "throw X(...);" terminates application if it can't allocate memory for exception being thrown. This happens with GCC. Standard does not specify what should happen in this situation (see C++11 15.1.p4).

I am trying to discuss this situation with someone who is closer to committee than me... Is it a defect? Standard does not specify behavior here and implementations are free to terminate (and they do) -- problem is that this makes impossibly to write a C++ program that survives out-of-memory situation -- even throwing std::bad_alloc can terminate your app.

This is an implementation-internal memory allocation. What could the Standard possibly say?

A lot of different things... For example:
"This allocation failure should result in std::bad_alloc being thrown. Implementation should never fail to throw std::bad_alloc"

Well that would change nothing, since "should" only specifies a recommendation ;) The word you're looking for is "must".

Naturally :-)
 

That being said, I don't think I would consider this a "defect" in the standard. It seems likely that this is deliberately left as an implementation detail. Basically, it's a quality-of-implementation issue; an implementation can provide a guarantee that all standard-library-generated `std::bad_alloc`s will be able to be generated. But they aren't required to do so.

I believe QoI isn't an issue here -- real issue is that standard does not allow std::bad_alloc to be thrown in this case (am I wrong?). Therefore implementations opt for std::terminate(). If it was allowed -- every exception specification would need to be implicitly extended with std::bad_alloc -- and I am pretty sure Standard doesn't mention that.

 
it certainly may come as a surprise to those who use exception specification, but at least I will be able to write application that doesn't mysteriously die when it runs out of memory

Unless it runs out of memory due to a stack allocation. In which case, you're in undefined behavior land. At least if an exception cannot be allocated, `std::terminate` will be called. With a stack allocation OOM error, you won't even get that.
 
I can control local variables and call depth in my code -- therefore I can avoid running out of stack. I can't avoid out-of-memory including running out of these emergency exception buffers, especially considering that now I can have many threads and retain (and chain) exceptions indefinitely via std::current_exception.



Thiago Macieira

unread,
Aug 15, 2017, 1:41:20 PM8/15/17
to std-dis...@isocpp.org
On Tuesday, 15 August 2017 08:43:36 PDT Michael Kilburn wrote:
> > That being said, I don't think I would consider this a "defect" in the
> > standard. It seems likely that this is deliberately left as an
> > implementation detail. Basically, it's a quality-of-implementation issue;
> > an implementation can provide a guarantee that all
> > standard-library-generated `std::bad_alloc`s will be able to be generated.
> > But they aren't *required* to do so.
>
> I believe QoI isn't an issue here -- real issue is that standard does not
> allow std::bad_alloc to be thrown in this case (am I wrong?). Therefore
> implementations opt for std::terminate(). If it was allowed -- every
> exception specification would need to be implicitly extended with
> std::bad_alloc -- and I am pretty sure Standard doesn't mention that.

Why would the standard forbid throwing? Where did you find the text that gives
you that impression?

Forget exception specifications, they are deprecated and removed from the
language now. There's only noexcept(true) and noexcept(false).

Michael Kilburn

unread,
Aug 15, 2017, 9:03:39 PM8/15/17
to ISO C++ Standard - Discussion
On Tuesday, August 15, 2017 at 10:28:08 AM UTC-5, Thiago Macieira wrote:
You should be able to modify the C++ support library's build to enable the buffer
allocation by default.

I don't see how this will help


Michael Kilburn

unread,
Aug 15, 2017, 9:33:43 PM8/15/17
to ISO C++ Standard - Discussion


On Tuesday, August 15, 2017 at 12:41:20 PM UTC-5, Thiago Macieira wrote:
On Tuesday, 15 August 2017 08:43:36 PDT Michael Kilburn wrote:
> I believe QoI isn't an issue here -- real issue is that standard does not
> allow std::bad_alloc to be thrown in this case (am I wrong?). Therefore
> implementations opt for std::terminate(). If it was allowed -- every
> exception specification would need to be implicitly extended with
> std::bad_alloc -- and I am pretty sure Standard doesn't mention that.

Why would the standard forbid throwing? Where did you find the text that gives
you that impression?

I can't find anything that forbids throwing another exception instead of one I specified in my throw statement. Though (especially since this possibility isn't mentioned) expectation is that it shouldn't happen

How about this argument:

        Leaving this case unspecified prevents me from writing portable code that handles OOM.

Is it good enough? One implementation will decide to throw bad_alloc, another -- runtime_error. There is no indication in standard wrt what to expect...

 
Forget exception specifications, they are deprecated and removed from the
language now. There's only noexcept(true) and noexcept(false).

Ok, so if you believe this isn't a language defect -- there was a followup question:

    Can you write C++ program that handles OOM and how?

I know for a fact that I can do it in C. Would really like to be able to do it in C++.


Nicol Bolas

unread,
Aug 15, 2017, 10:12:36 PM8/15/17
to ISO C++ Standard - Discussion
On Tuesday, August 15, 2017 at 11:43:37 AM UTC-4, Michael Kilburn wrote:
On Tuesday, August 15, 2017 at 10:28:28 AM UTC-5, Nicol Bolas wrote:
On Tuesday, August 15, 2017 at 11:18:55 AM UTC-4, Michael Kilburn wrote:
On Tuesday, August 15, 2017 at 7:18:16 AM UTC-5, Chris Hallock wrote:
On Tuesday, August 15, 2017 at 3:33:08 AM UTC-4, Michael Kilburn wrote:
I have discovered recently that "throw X(...);" terminates application if it can't allocate memory for exception being thrown. This happens with GCC. Standard does not specify what should happen in this situation (see C++11 15.1.p4).

I am trying to discuss this situation with someone who is closer to committee than me... Is it a defect? Standard does not specify behavior here and implementations are free to terminate (and they do) -- problem is that this makes impossibly to write a C++ program that survives out-of-memory situation -- even throwing std::bad_alloc can terminate your app.

This is an implementation-internal memory allocation. What could the Standard possibly say?

A lot of different things... For example:
"This allocation failure should result in std::bad_alloc being thrown. Implementation should never fail to throw std::bad_alloc"

Well that would change nothing, since "should" only specifies a recommendation ;) The word you're looking for is "must".

Naturally :-)
 

That being said, I don't think I would consider this a "defect" in the standard. It seems likely that this is deliberately left as an implementation detail. Basically, it's a quality-of-implementation issue; an implementation can provide a guarantee that all standard-library-generated `std::bad_alloc`s will be able to be generated. But they aren't required to do so.

I believe QoI isn't an issue here -- real issue is that standard does not allow std::bad_alloc to be thrown in this case (am I wrong?).

I'm really confused by this.

As I understood the issue, your concern was that you attempt to call an allocation function, which fails to allocate the requested memory. So the implementation attempts to throw `std::bad_alloc`, as required by the standard, but because throwing exceptions may require dynamic allocations, the implementation may fail to throw that exception.

If this happens, the standard cannot "allow std::bad_alloc to be thrown", because if the implementation could throw it, you wouldn't have this problem.

That's what makes this a QOI issue. The standard does not require that throwing exceptions use dynamic allocations; that's merely a possible implementation. Therefore, a C++ implementation could make sure that the executable contains sufficient static memory to throw a single `std::bad_alloc` exception.

Therefore implementations opt for std::terminate(). If it was allowed -- every exception specification would need to be implicitly extended with std::bad_alloc -- and I am pretty sure Standard doesn't mention that.

That's Java, not C++. In C++, when we had exception specifications, if nothing was listed, you throw anything.

it certainly may come as a surprise to those who use exception specification, but at least I will be able to write application that doesn't mysteriously die when it runs out of memory

Unless it runs out of memory due to a stack allocation. In which case, you're in undefined behavior land. At least if an exception cannot be allocated, `std::terminate` will be called. With a stack allocation OOM error, you won't even get that.
 
I can control local variables and call depth in my code -- therefore I can avoid running out of stack. I can't avoid out-of-memory including running out of these emergency exception buffers, especially considering that now I can have many threads and retain (and chain) exceptions indefinitely via std::current_exception.

Nonsense. You have just as much control over your memory allocation patterns and what exceptions get thrown as you do over local variable size and call stack depth. If `std::bad_alloc` is thrown, it is your choice how much memory to allocate during the stack unwinding process to reach the cite where the exception is caught.

Nicol Bolas

unread,
Aug 15, 2017, 10:14:35 PM8/15/17
to ISO C++ Standard - Discussion

Catch `std::bad_alloc` at the appropriate locations. It's easier than in C.

Michael Kilburn

unread,
Aug 15, 2017, 11:00:59 PM8/15/17
to ISO C++ Standard - Discussion
Well, it doesn't work -- app gets terminated (GCC) and according to standard it is ok. I don't see how this constitutes "handling OOM" .

Patrice Roy

unread,
Aug 15, 2017, 11:02:44 PM8/15/17
to std-dis...@isocpp.org
If the issue is whether to do some last chance action or not, catch(...) is probably your friend. When everything fails, fine diagnostics might not cut it, but taking some action such as closing down a nuclear reactor can be interesting.

As was mentioned before, the behavior of the underlying exception handling mechanism, allocating memory or not, is not something the standard imposes. If you are dealing with an open source implementation, you can probably get the control you need.

Out of curiosity, have you seen this presentation on surviving bad_alloc? https://www.youtube.com/watch?v=QIiFsqsb9HM (maybe it doesn't relate to your use-case, but just in case... :) )

--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+unsubscribe@isocpp.org.
To post to this group, send email to std-dis...@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.

Michael Kilburn

unread,
Aug 15, 2017, 11:08:30 PM8/15/17
to ISO C++ Standard - Discussion


On Tuesday, August 15, 2017 at 9:12:36 PM UTC-5, Nicol Bolas wrote:
That being said, I don't think I would consider this a "defect" in the standard. It seems likely that this is deliberately left as an implementation detail. Basically, it's a quality-of-implementation issue; an implementation can provide a guarantee that all standard-library-generated `std::bad_alloc`s will be able to be generated. But they aren't required to do so.

I believe QoI isn't an issue here -- real issue is that standard does not allow std::bad_alloc to be thrown in this case (am I wrong?).

I'm really confused by this.

As I understood the issue, your concern was that you attempt to call an allocation function, which fails to allocate the requested memory. So the implementation attempts to throw `std::bad_alloc`, as required by the standard, but because throwing exceptions may require dynamic allocations, the implementation may fail to throw that exception.

No. My concern is that "throw my_exception();" statement has unspecified behavior if exception mechanism fails to allocate memory for my_exception object. Most popular compilers seem to simply terminate application.

 
If this happens, the standard cannot "allow std::bad_alloc to be thrown", because if the implementation could throw it, you wouldn't have this problem.

That's what makes this a QOI issue. The standard does not require that throwing exceptions use dynamic allocations; that's merely a possible implementation. Therefore, a C++ implementation could make sure that the executable contains sufficient static memory to throw a single `std::bad_alloc` exception.

Therefore implementations opt for std::terminate(). If it was allowed -- every exception specification would need to be implicitly extended with std::bad_alloc -- and I am pretty sure Standard doesn't mention that.

That's Java, not C++. In C++, when we had exception specifications, if nothing was listed, you throw anything.

I know, but if standard mandate throwing bad_alloc in this case code like this:

    void foo() throw(int) { throw 1; }

needs to be changed to:

    void foo() throw(int, std::bad_alloc) { throw 1; }

either manually in every instance or implicitly (by a decree in standard which would say something like "any function that throws is considered to have std::bad_alloc added to it's exception specification"). Because such decree is nowhere to be found in the standard and no one ever bothered adding std::bad_alloc to function exception specification -- I assume that no one ever considered throwing std::bad_alloc from "throw my_exception()".


 
it certainly may come as a surprise to those who use exception specification, but at least I will be able to write application that doesn't mysteriously die when it runs out of memory

Unless it runs out of memory due to a stack allocation. In which case, you're in undefined behavior land. At least if an exception cannot be allocated, `std::terminate` will be called. With a stack allocation OOM error, you won't even get that.
 
I can control local variables and call depth in my code -- therefore I can avoid running out of stack. I can't avoid out-of-memory including running out of these emergency exception buffers, especially considering that now I can have many threads and retain (and chain) exceptions indefinitely via std::current_exception.

Nonsense. You have just as much control over your memory allocation patterns and what exceptions get thrown as you do over local variable size and call stack depth. If `std::bad_alloc` is thrown, it is your choice how much memory to allocate during the stack unwinding process to reach the cite where the exception is caught.

I think you totally misunderstood my point. 

Nicol Bolas

unread,
Aug 15, 2017, 11:09:38 PM8/15/17
to ISO C++ Standard - Discussion

Are you sure the problem is the inability of the implementation to throw an exception? What evidence do you have to support that conclusion?

Michael Kilburn

unread,
Aug 15, 2017, 11:21:20 PM8/15/17
to ISO C++ Standard - Discussion
On Tuesday, August 15, 2017 at 10:02:44 PM UTC-5, Patrice Roy wrote:
If the issue is whether to do some last chance action or not, catch(...) is probably your friend. When everything fails, fine diagnostics might not cut it, but taking some action such as closing down a nuclear reactor can be interesting.

Once again, this code can terminate in GCC in low memory situation before executing catch block:

void foo()
try
{
    int k = read_user_input()
    if (k <= 0)
        throw std::runtime_error("Value has to be greater than zero");
}
catch(...)
{
    lower_reactor_rods();
}

and according to my reading of standard it is an acceptable behavior. I am looking for answer to these questions:
- why is it like this?
- is it possible to write C++ program (that throws exceptions) that is capable of reliably handling out-of-memory?

 

As was mentioned before, the behavior of the underlying exception handling mechanism, allocating memory or not, is not something the standard imposes. If you are dealing with an open source implementation, you can probably get the control you need.

If this is true -- this makes it impossible to write code that reliably handles OOMs (unless I am missing smth very obvious). It seems like a deficiency in standard and (unless there is a very good reason not to) it needs to be fixed.

 
Out of curiosity, have you seen this presentation on surviving bad_alloc? https://www.youtube.com/watch?v=QIiFsqsb9HM (maybe it doesn't relate to your use-case, but just in case... :) )

Yes, I saw it. In fact I (with my friend) built at least one system that was surviving std::bad_alloc on regular basis (obscure stock trading engine). I was very proud of that system and thought that it is bullet-proof -- turned out it isn't, it still can crash.

 

2017-08-15 22:14 GMT-04:00 Nicol Bolas <jmck...@gmail.com>:
On Tuesday, August 15, 2017 at 9:33:43 PM UTC-4, Michael Kilburn wrote:
On Tuesday, August 15, 2017 at 12:41:20 PM UTC-5, Thiago Macieira wrote:
Forget exception specifications, they are deprecated and removed from the
language now. There's only noexcept(true) and noexcept(false).

Ok, so if you believe this isn't a language defect -- there was a followup question:

    Can you write C++ program that handles OOM and how?

I know for a fact that I can do it in C. Would really like to be able to do it in C++.

Catch `std::bad_alloc` at the appropriate locations. It's easier than in C.

--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussio...@isocpp.org.

Michael Kilburn

unread,
Aug 15, 2017, 11:25:56 PM8/15/17
to ISO C++ Standard - Discussion
I have three pieces of evidence:
- my copy of C++11 standard that leaves behavior in this case unspecified (according to my reading)
- trivial code that tries to allocate very big exception:
- Itanium C++ ABI that is apparently used by GCC and Clang that mandates std::terminate in this case:


Nicol Bolas

unread,
Aug 15, 2017, 11:47:15 PM8/15/17
to ISO C++ Standard - Discussion

... OK, I think I see what you're saying. You're not talking about when `std::bad_alloc` would be thrown but can't be thrown due to lack of memory. You're talking about when throwing an unrelated exception fails due to lack of memory; that the exception is what is causing the lack of memory.

In that case, there is logically nothing that can be done. You're basically talking about a failure of the system's ability to report a failure. Functionally, it's no different from having the copy/move constructor for an exception type emit an exception. If the mechanics involved in a throw expression fail (whether it's the copy/move into the exception object or the allocation of the exception object), then that means the system is too broken to continue.

Nicol Bolas

unread,
Aug 15, 2017, 11:59:24 PM8/15/17
to ISO C++ Standard - Discussion
On Tuesday, August 15, 2017 at 11:21:20 PM UTC-4, Michael Kilburn wrote:
On Tuesday, August 15, 2017 at 10:02:44 PM UTC-5, Patrice Roy wrote:
If the issue is whether to do some last chance action or not, catch(...) is probably your friend. When everything fails, fine diagnostics might not cut it, but taking some action such as closing down a nuclear reactor can be interesting.

Once again, this code can terminate in GCC in low memory situation before executing catch block:

void foo()
try
{
    int k = read_user_input()
    if (k <= 0)
        throw std::runtime_error("Value has to be greater than zero");
}
catch(...)
{
    lower_reactor_rods();
}

and according to my reading of standard it is an acceptable behavior. I am looking for answer to these questions:
- why is it like this?

Because when the ability to report errors stops working, the ability to report errors has stopped working. ;) Much like if a program issues a wild write that happens to land on `errno`'s storage, you can't rely on the value of `errno` to be consistent with expectations.

This is also why such a scenario calls `std::terminate`; this function exists for dealing with "things got really broken" scenarios. You can register your own termination function, and thus be able to deal with catastrophic conditions.

- is it possible to write C++ program (that throws exceptions) that is capable of reliably handling out-of-memory?

"Reliably" is of course in the eyes of the beholder. The above code is quite reliable. Indeed, implementations might even guarantee that it works. If you're in a safety critical scenario, you always register a terminate handler.

On a related matter:

 On Tuesday, August 15, 2017 at 11:08:30 PM UTC-4, Michael Kilburn wrote:
On Tuesday, August 15, 2017 at 9:12:36 PM UTC-5, Nicol Bolas wrote:
Therefore implementations opt for std::terminate(). If it was allowed -- every exception specification would need to be implicitly extended with std::bad_alloc -- and I am pretty sure Standard doesn't mention that.
That's Java, not C++. In C++, when we had exception specifications, if nothing was listed, you throw anything.

I know, but if standard mandate throwing bad_alloc in this case code like this:

You've merely restated reason #4,658 as to why C++ doesn't have this feature anymore ;)

Michael Kilburn

unread,
Aug 15, 2017, 11:59:50 PM8/15/17
to ISO C++ Standard - Discussion
Not exactly -- from my (admittedly limited) point of view nothing prevents Standard from mandating smth like this:

    "If implementation fails to allocate memory in this case -- std::bad_alloc must be thrown. Throwing std::bad_alloc must not fail."


and implementation can simply pre-can std::bad_alloc object and always use it when object of this type is thrown. Or treat std::bad_alloc case as smth special (and pass NULL internally -- smth that doesn't need to touch heap). In old times you'd also need to update related section of standard that deals with exception specifications, but nowadays it is not required (since that feature was deprecated).


Questions is: what prevents standard from doing this? why it leaves this case unspecified?

I keep asking around but can't get any good answers.

Nicol Bolas

unread,
Aug 16, 2017, 12:06:13 AM8/16/17
to ISO C++ Standard - Discussion

Because it doesn't work. You can have multiple exceptions in flight. You can have multiple `bad_alloc` exceptions in flight. Each of those objects will need separate and distinct storage, since they are separate and distinct objects.

And therefore, you will run out of that "smth that doesn't need to touch heap" memory. At which point, you'll be right back here at `std::terminate`.

So your suggestion solves nothing.

Michael Kilburn

unread,
Aug 16, 2017, 12:07:29 AM8/16/17
to ISO C++ Standard - Discussion
On Tuesday, August 15, 2017 at 10:59:24 PM UTC-5, Nicol Bolas wrote:
On Tuesday, August 15, 2017 at 11:21:20 PM UTC-4, Michael Kilburn wrote:
On Tuesday, August 15, 2017 at 10:02:44 PM UTC-5, Patrice Roy wrote:
If the issue is whether to do some last chance action or not, catch(...) is probably your friend. When everything fails, fine diagnostics might not cut it, but taking some action such as closing down a nuclear reactor can be interesting.

Once again, this code can terminate in GCC in low memory situation before executing catch block:

void foo()
try
{
    int k = read_user_input()
    if (k <= 0)
        throw std::runtime_error("Value has to be greater than zero");
}
catch(...)
{
    lower_reactor_rods();
}

and according to my reading of standard it is an acceptable behavior. I am looking for answer to these questions:
- why is it like this?

Because when the ability to report errors stops working, the ability to report errors has stopped working. ;) Much like if a program issues a wild write that happens to land on `errno`'s storage, you can't rely on the value of `errno` to be consistent with expectations.

I am not convinced that "ability to report errors stop working" (see my answer to your other message). After all, I don't need to allocate memory to pass flag that there is no memory left. Why do you believe that this case can't be handled?


This is also why such a scenario calls `std::terminate`; this function exists for dealing with "things got really broken" scenarios. You can register your own termination function, and thus be able to deal with catastrophic conditions.

- is it possible to write C++ program (that throws exceptions) that is capable of reliably handling out-of-memory?

"Reliably" is of course in the eyes of the beholder. The above code is quite reliable. Indeed, implementations might even guarantee that it works. If you're in a safety critical scenario, you always register a terminate handler.

Yes, but it seems that most popular implementations decided not to provide this guarantee. And you can't do anything useful in terminate handler.
 

On a related matter: 
 On Tuesday, August 15, 2017 at 11:08:30 PM UTC-4, Michael Kilburn wrote:
On Tuesday, August 15, 2017 at 9:12:36 PM UTC-5, Nicol Bolas wrote:
Therefore implementations opt for std::terminate(). If it was allowed -- every exception specification would need to be implicitly extended with std::bad_alloc -- and I am pretty sure Standard doesn't mention that.

That's Java, not C++. In C++, when we had exception specifications, if nothing was listed, you throw anything.

I know, but if standard mandate throwing bad_alloc in this case code like this:

You've merely restated reason #4,658 as to why C++ doesn't have this feature anymore ;)

I wasn't a big fan of that feature anyway :)

 

Michael Kilburn

unread,
Aug 16, 2017, 12:12:30 AM8/16/17
to ISO C++ Standard - Discussion
Why each std::bad_alloc instance has to use a separate and distinct storage? What prevents standard from mandating that each "throw std::bad_alloc()" has to use the same instance? It is not like it is mutable...

Plus, I imagine there might be other ways to deal with this problem. Effectively all we need to do is to pass a 'no memory' flag to another function (that unwinds stack, matches exception types against one mentioned in catch block and etc).


Chris Hallock

unread,
Aug 16, 2017, 1:25:23 AM8/16/17
to ISO C++ Standard - Discussion
On Tuesday, August 15, 2017 at 11:18:55 AM UTC-4, Michael Kilburn wrote:
On Tuesday, August 15, 2017 at 7:18:16 AM UTC-5, Chris Hallock wrote:
On Tuesday, August 15, 2017 at 3:33:08 AM UTC-4, Michael Kilburn wrote:
I have discovered recently that "throw X(...);" terminates application if it can't allocate memory for exception being thrown. This happens with GCC. Standard does not specify what should happen in this situation (see C++11 15.1.p4). [...]

This is an implementation-internal memory allocation. What could the Standard possibly say?

A lot of different things... For example:
"This allocation failure should result in std::bad_alloc being thrown. Implementation should never fail to throw std::bad_alloc"

The Standard can't prohibit the existence of implementation limits nor define the behavior of programs that exceed those limits. The statement "Implementation should never fail to throw std::bad_alloc" can't be absolutely guaranteed no matter how forcefully you say it, because there are all manner of ways for programs to fail unexpectedly that are not within the power of compiler authors (or, indeed, anyone) to prevent. For example, the user could apply a sledgehammer to the computer running your program.

Michael Kilburn

unread,
Aug 16, 2017, 1:37:57 AM8/16/17
to ISO C++ Standard - Discussion
I don't think this is a good argument -- we operate on assumption that hardware delivers promised guarantees. There are multiple places in the language where it mandates certain outcomes and disregards "sledgehammer" factor. In my (limited) understanding there at least one way to implement "throw std::bad_alloc() should never fail" requirement from compiler standpoint.

Thiago Macieira

unread,
Aug 16, 2017, 2:34:25 AM8/16/17
to std-dis...@isocpp.org
How won't it? If you make it work like you want it, then it will.

What part am I missing?

Thiago Macieira

unread,
Aug 16, 2017, 2:37:24 AM8/16/17
to std-dis...@isocpp.org
On Tuesday, 15 August 2017 18:33:43 PDT Michael Kilburn wrote:
> Leaving this case unspecified prevents me from writing portable
> code that handles OOM.

You can't portably handle OOM anyway.

On a modern, multitasking operating system with virtual memory, OOM is
signalled by killing some processes. You don't get notified by it. Your memory
allocations succeed, but you may get SIGBUS when you try to access it.

Thiago Macieira

unread,
Aug 16, 2017, 2:46:05 AM8/16/17
to std-dis...@isocpp.org
On Tuesday, 15 August 2017 20:08:30 PDT Michael Kilburn wrote:
> No. My concern is that "throw my_exception();" statement has unspecified
> behavior if exception mechanism fails to allocate memory for my_exception
> object. Most popular compilers seem to simply terminate application.

It's not unspecified behaviour. It's very specified: call std::terminate.

As others have said: when the mechanism used to report failures fails itself,
you get a last-ditch bail-out the form of std::terminate.

It's actually the same behaviour as a double-throw: if in the process of
throwing an exception, we ran out of memory, we'd throw std::bad_alloc. But
since an exception is already being thrown, we enter the double-throw
scenario. Result: call std::terminate.

Thiago Macieira

unread,
Aug 16, 2017, 2:46:59 AM8/16/17
to std-dis...@isocpp.org
On Tuesday, 15 August 2017 22:37:57 PDT Michael Kilburn wrote:
> I don't think this is a good argument -- we operate on assumption that
> hardware delivers promised guarantees. There are multiple places in the
> language where it mandates certain outcomes and disregards "sledgehammer"
> factor. In my (limited) understanding there at least one way to implement
> "throw std::bad_alloc() should never fail" requirement from compiler
> standpoint.

That wouldn't solve your problem.

Michael Kilburn

unread,
Aug 16, 2017, 2:55:21 AM8/16/17
to ISO C++ Standard - Discussion


On Wednesday, August 16, 2017 at 1:46:59 AM UTC-5, Thiago Macieira wrote:
On Tuesday, 15 August 2017 22:37:57 PDT Michael Kilburn wrote:
> I don't think this is a good argument -- we operate on assumption that
> hardware delivers promised guarantees. There are multiple places in the
> language where it mandates certain outcomes and disregards "sledgehammer"
> factor. In my (limited) understanding there at least one way to implement
> "throw std::bad_alloc() should never fail" requirement from compiler
> standpoint.

That wouldn't solve your problem.

Why do you think so? 

Michael Kilburn

unread,
Aug 16, 2017, 2:58:16 AM8/16/17
to ISO C++ Standard - Discussion


On Wednesday, August 16, 2017 at 1:46:05 AM UTC-5, Thiago Macieira wrote:
On Tuesday, 15 August 2017 20:08:30 PDT Michael Kilburn wrote:
> No. My concern is that "throw my_exception();" statement has unspecified
> behavior if exception mechanism fails to allocate memory for my_exception
> object. Most popular compilers seem to simply terminate application.

It's not unspecified behaviour. It's very specified: call std::terminate.

Can you point me to a spot in a standard that says that?

 
As others have said: when the mechanism used to report failures fails itself,
you get a last-ditch bail-out the form of std::terminate.

No, you don't. Implementation can certainly do that, but it isn't even mentioned in standard. As I mentioned in original post -- standard leaves this to implementation (according to my reading), see C++11 15.1.p4

Michael Kilburn

unread,
Aug 16, 2017, 3:00:32 AM8/16/17
to ISO C++ Standard - Discussion


On Wednesday, August 16, 2017 at 1:37:24 AM UTC-5, Thiago Macieira wrote:
On Tuesday, 15 August 2017 18:33:43 PDT Michael Kilburn wrote:
>         Leaving this case unspecified prevents me from writing portable
> code that handles OOM.

You can't portably handle OOM anyway.

I can limit my memory allocations (by intercepting malloc), I can run 32bit app on 64 bit machine with 16GB of memory, and etc.

 
 On a modern, multitasking operating system with virtual memory, OOM is 
signalled by killing some processes. You don't get notified by it. Your memory
allocations succeed, but you may get SIGBUS when you try to access it.

Yes, everyone knows about OOM-killer. Linux can be configured to overcommit -- in this case apps aren't killed.

Michael Kilburn

unread,
Aug 16, 2017, 3:05:21 AM8/16/17
to ISO C++ Standard - Discussion


On Wednesday, August 16, 2017 at 1:34:25 AM UTC-5, Thiago Macieira wrote:
On Tuesday, 15 August 2017 18:03:39 PDT Michael Kilburn wrote:
> On Tuesday, August 15, 2017 at 10:28:08 AM UTC-5, Thiago Macieira wrote:
> > You should be able to modify the C++ support library's build to enable the
> > buffer
> > allocation by default.
>
> I don't see how this will help

How won't it? If you make it work like you want it, then it will.

What part am I missing?

I think you confuse my problem with another one. What I am saying is that throwing any exception can have unspecified behavior under low memory conditions. 'Unspecified' means standard doesn't mandate certain outcome and leaves it to implementation. Most popular ones -- call std::terminate. This means I can't write a (portable) program that reliably handles out-of-memory condition.

Activating buffer allocation doesn't help really -- first of all it is not portable, second -- you still end up with limited resource that can run out. Instead of trying to play race with environment, I'd rather have a reliable way to handle this situation in the code.

Does it make sense?

Ville Voutilainen

unread,
Aug 16, 2017, 5:02:03 AM8/16/17
to std-dis...@isocpp.org
On 16 August 2017 at 10:05, Michael Kilburn <crusad...@gmail.com> wrote:
> I think you confuse my problem with another one. What I am saying is that
> throwing any exception can have unspecified behavior under low memory
> conditions. 'Unspecified' means standard doesn't mandate certain outcome and
> leaves it to implementation. Most popular ones -- call std::terminate. This
> means I can't write a (portable) program that reliably handles out-of-memory
> condition.

Your earlier example runs out of stack space. See how it behaves if
dynamic allocation is
done instead:
https://wandbox.org/permlink/wLk01q0LnzEdFJvS

The standard can't mandate that constructing an exception cannot
consume stack space.
You can't avoid stack exhaustion in a portable manner, because
detecting it and recovering from it
has a cost that some users would not want to pay, and might not even
be possible on some platforms.

>
> Activating buffer allocation doesn't help really -- first of all it is not
> portable, second -- you still end up with limited resource that can run out.
> Instead of trying to play race with environment, I'd rather have a reliable
> way to handle this situation in the code.
>
> Does it make sense?

Your problem has nothing particular to do with exceptions.

Ville Voutilainen

unread,
Aug 16, 2017, 5:03:51 AM8/16/17
to std-dis...@isocpp.org
On 16 August 2017 at 09:58, Michael Kilburn <crusad...@gmail.com> wrote:
>
>
> On Wednesday, August 16, 2017 at 1:46:05 AM UTC-5, Thiago Macieira wrote:
>>
>> On Tuesday, 15 August 2017 20:08:30 PDT Michael Kilburn wrote:
>> > No. My concern is that "throw my_exception();" statement has unspecified
>> > behavior if exception mechanism fails to allocate memory for
>> > my_exception
>> > object. Most popular compilers seem to simply terminate application.
>>
>> It's not unspecified behaviour. It's very specified: call std::terminate.
>
>
> Can you point me to a spot in a standard that says that?


There is no such spot. Throwing an exception during unwinding calls
terminate, but
failing to allocate memory when an exception is initialized before
it's thrown does not.

Thiago Macieira

unread,
Aug 16, 2017, 11:32:39 AM8/16/17
to std-dis...@isocpp.org
On Tuesday, 15 August 2017 23:55:21 PDT Michael Kilburn wrote:
> > > "throw std::bad_alloc() should never fail" requirement from compiler
> > > standpoint.
> >
> > That wouldn't solve your problem.
>
> Why do you think so?

Because you said your problem is that you wanted to throw something else, an
exception class possibly much bigger than std::bad_alloc. So even if the
implementation is required to be able to throw std::bad_alloc, it won't be
required to always be able to throw your type.

Thiago Macieira

unread,
Aug 16, 2017, 11:43:05 AM8/16/17
to std-dis...@isocpp.org
On Wednesday, 16 August 2017 00:00:32 PDT Michael Kilburn wrote:
> On Wednesday, August 16, 2017 at 1:37:24 AM UTC-5, Thiago Macieira wrote:
> > On Tuesday, 15 August 2017 18:33:43 PDT Michael Kilburn wrote:
> > > Leaving this case unspecified prevents me from writing portable
> > >
> > > code that handles OOM.
> >
> > You can't portably handle OOM anyway.
>
> I can limit my memory allocations (by intercepting malloc), I can run 32bit
> app on 64 bit machine with 16GB of memory, and etc.

None of which guard you against another process consuming 15.99 GB of RAM.

> > On a modern, multitasking operating system with virtual memory, OOM is
> > signalled by killing some processes. You don't get notified by it. Your
> > memory
> > allocations succeed, but you may get SIGBUS when you try to access it.
>
> Yes, everyone knows about OOM-killer. Linux can be configured to overcommit
> -- in this case apps aren't killed.

Did you mean "not overcommit"? Because if you do configure Linux to overcommit,
then brk() and anonymous mmap may succeed, but then fail to deliver when the
pages are faulted. If that's the case, you get a SIGBUS.

But even in non-overcommit configuration, I'm pretty sure you can cause the
kernel to commit to more, such as by mapping files, creating files on a tmpfs or
using memfd.

My point is that it's extremely difficult to deal with OOM conditions in a *non*
portable manner by fiddling with your OOM score adjustment, doing privileged-
user operations like mlock() or changing the overcommit settings, or even
compiling your own kernel. If you are prepared to do all that, then you should
be able to deal with the platform-specific way of throwing exceptions too.

Andrey Semashev

unread,
Aug 16, 2017, 11:54:57 AM8/16/17
to std-dis...@isocpp.org
On 08/16/17 18:43, Thiago Macieira wrote:
>
> My point is that it's extremely difficult to deal with OOM conditions in a *non*
> portable manner by fiddling with your OOM score adjustment, doing privileged-
> user operations like mlock() or changing the overcommit settings, or even
> compiling your own kernel. If you are prepared to do all that, then you should
> be able to deal with the platform-specific way of throwing exceptions too.

Although I agree with the status quo that you have to deal with
platform-specific stuff to handle OOM gracefully, I disagree with the
"let's just not handle it" response to the problem. And no, calling
std::terminate or deadlocking when one tries to throw an exception is
not a good solution in my view. Throwing std::bad_alloc (reliably)
instead of user's exception would be.

I think the language has to mandate some sort of escape latch for
std::bad_alloc, even if it means special casing this particular
exception in some ways. Although I realize that that would cause ABI
breakage and therefore would not be an easy change.

Thiago Macieira

unread,
Aug 16, 2017, 12:01:54 PM8/16/17
to std-dis...@isocpp.org
On Wednesday, 16 August 2017 08:54:52 PDT Andrey Semashev wrote:
> Although I agree with the status quo that you have to deal with
> platform-specific stuff to handle OOM gracefully, I disagree with the
> "let's just not handle it" response to the problem. And no, calling
> std::terminate or deadlocking when one tries to throw an exception is
> not a good solution in my view. Throwing std::bad_alloc (reliably)
> instead of user's exception would be.
>
> I think the language has to mandate some sort of escape latch for
> std::bad_alloc, even if it means special casing this particular
> exception in some ways. Although I realize that that would cause ABI
> breakage and therefore would not be an easy change.

I agree that the language should mandate some sort of escape latch, but I
think it should be std::terminate. That's how we deal with double faults
elsewhere and this is a double fault.

Still, the text of the language needs to be careful, because other things
besides OOM can cause the exception/unwind system to fail. For example, if you
are in a stack overflow condition, you can't *call* __cxa_throw(), so you can't
throw. If the unwind tables have been damaged or don't cover the piece of code
being unwound (fallen off the edge of a function? called a faulty pointer?),
you can't unwind either.

In other words, we need an escape latch for the escape latch (an exception to
the exception to throwing exceptions) which is back again to allowed to fail.

Nicol Bolas

unread,
Aug 16, 2017, 12:17:42 PM8/16/17
to ISO C++ Standard - Discussion

If you're going to special case this situation, then it should not throw `std::bad_alloc`. That exception is specifically for normal memory allocation failures. Users need to be able to differentiate between "I could not allocated what you requested" and "I could not throw an exception." One of these is a normal allocation failure; the other is a failure of the exception system to do what it was told.

Why does there need to be a difference? Because being OOM does not mean that the exception system will certainly fail. Similarly, the exception system can fail despite not being OOM. You can have a de-facto overflow of the exception system by having "too many" in flight, but how many that is is implementation-dependent.

If you have an exception failure, you can no longer reliably throw exceptions. If you have an allocation failure, you may well be able to throw exceptions.

Andrey Semashev

unread,
Aug 16, 2017, 12:26:55 PM8/16/17
to std-dis...@isocpp.org
On 08/16/17 19:01, Thiago Macieira wrote:
> On Wednesday, 16 August 2017 08:54:52 PDT Andrey Semashev wrote:
>> Although I agree with the status quo that you have to deal with
>> platform-specific stuff to handle OOM gracefully, I disagree with the
>> "let's just not handle it" response to the problem. And no, calling
>> std::terminate or deadlocking when one tries to throw an exception is
>> not a good solution in my view. Throwing std::bad_alloc (reliably)
>> instead of user's exception would be.
>>
>> I think the language has to mandate some sort of escape latch for
>> std::bad_alloc, even if it means special casing this particular
>> exception in some ways. Although I realize that that would cause ABI
>> breakage and therefore would not be an easy change.
>
> I agree that the language should mandate some sort of escape latch, but I
> think it should be std::terminate. That's how we deal with double faults
> elsewhere and this is a double fault.

std::terminate leaves no room for recovery, that's my main problem with
it. And the user's code may be written in a way that makes recovery
possible, if only in majority of cases.

> Still, the text of the language needs to be careful, because other things
> besides OOM can cause the exception/unwind system to fail. For example, if you
> are in a stack overflow condition, you can't *call* __cxa_throw(), so you can't
> throw. If the unwind tables have been damaged or don't cover the piece of code
> being unwound (fallen off the edge of a function? called a faulty pointer?),
> you can't unwind either.

Damaged stack or unwind tables are only possible as a result of program
misbehavior or miscompilation, the language need not guarantee anything
in this case. OOM, on the other hand, can happen for a perfectly valid
program, so the language has to offer the programmer ways to handle it
gracefully.

Stack overflow I tend to consider as a result of misbehavior as well, as
it is unlikely to be a result of a well behaved program, but I could
probably be convinced otherwise on this particular case. In any case,
stack overflow is a separate problem that is not related to exceptions,
and it may have a different solution. For example, the language could
introduce another exception for this purpose, and the implementation
would be bound to always reserve enough stack space to be able to throw
this exception.

> In other words, we need an escape latch for the escape latch (an exception to
> the exception to throwing exceptions) which is back again to allowed to fail.

I don't think so. For example, allowing the implementation to throw
shared pre-allocated instance of std::bad_alloc might be the solution to
the particular problem with exceptions. The other problems you mentioned
either don't need to be solved or can have different solutions.

Andrey Semashev

unread,
Aug 16, 2017, 12:38:55 PM8/16/17
to std-dis...@isocpp.org
On 08/16/17 19:17, Nicol Bolas wrote:
> On Wednesday, August 16, 2017 at 11:54:57 AM UTC-4, Andrey Semashev wrote:
>
> I think the language has to mandate some sort of escape latch for
> std::bad_alloc, even if it means special casing this particular
> exception in some ways. Although I realize that that would cause ABI
> breakage and therefore would not be an easy change.
>
> If you're going to special case this situation, then it /should not/
> throw `std::bad_alloc`. That exception is specifically for normal memory
> allocation failures. Users need to be able to differentiate between "I
> could not allocated what you requested" and "I could not throw an
> exception." One of these is a normal allocation failure; the other is a
> failure of the exception system to do what it was told.
>
> Why does there need to be a difference? Because being OOM does not mean
> that the exception system will /certainly/ fail. Similarly, the
> exception system can fail /despite/ not being OOM. You can have a
> de-facto overflow of the exception system by having "too many" in
> flight, but how many that is is implementation-dependent.

I'm not opposed to the idea of having a different exception to indicate
errors other than OOM from the language support system (we already have
a few), although so far we didn't have a need for one. Maybe I'm wrong,
but I don't remember reading/hearing compiler vendor complaints about
inability to report errors from the exception handling system, other
than OOM. Anyways, I'm not discussing those other kinds of errors here.

But if the exception handling system fails exactly because of the OOM,
it would be most natural for it to throw std::bad_alloc. It would also
be natural for the user's code to handle the exception in the
std::bad_alloc handler regardless of where exactly memory depletion has
been detected - whether that is the language support library, standard
library or user's code.

Nicol Bolas

unread,
Aug 16, 2017, 1:24:45 PM8/16/17
to ISO C++ Standard - Discussion

Here's the thing. In every other place you catch `bad_alloc`, you have at least the theoretical power to resolve the problem by deallocating stuff. `bad_alloc` is thrown by dynamic allocations.

If you catch the "I can't throw an exception" exception, you can deallocate every byte of dynamically allocated memory you use, but that will not guarantee that you can still get an exception thrown. This is because exceptions don't have to come from the same pool as regular dynamic allocations.

The cause of the problem is different, what you have to do to resolve it may be different, and therefore it should be a different type.

Andrey Semashev

unread,
Aug 16, 2017, 1:36:51 PM8/16/17
to std-dis...@isocpp.org
On 08/16/17 20:24, Nicol Bolas wrote:
> On Wednesday, August 16, 2017 at 12:38:55 PM UTC-4, Andrey Semashev wrote:
>
> But if the exception handling system fails exactly because of the OOM,
> it would be most natural for it to throw std::bad_alloc. It would also
> be natural for the user's code to handle the exception in the
> std::bad_alloc handler regardless of where exactly memory depletion has
> been detected - whether that is the language support library, standard
> library or user's code.
>
> Here's the thing. In every other place you catch `bad_alloc`, you have
> at least the theoretical power to resolve the problem by deallocating
> stuff. `bad_alloc` is thrown by dynamic allocations.
>
> If you catch the "I can't throw an exception" exception, you can
> deallocate every byte of dynamically allocated memory you use, but that
> /will not/ guarantee that you can still get an exception thrown. This is
> because exceptions don't /have/ to come from the same pool as regular
> dynamic allocations.
>
> The cause of the problem is different, what you have to do to resolve it
> may be different, and therefore it should be a different type.

Ok, fair enough. Let me put it another way then. If the implementation
uses conventional heap for throwing exceptions (i.e. in order to resolve
the problem the user can release heap memory) then it should throw
std::bad_alloc. If it uses another kind of storage then it should throw
another type of exception. The standard would describe both
std::bad_alloc and this new exception and say that std::bad_alloc is
thrown when conventional heap is exhausted and that other exception when
other (implementation-defined) kinds of resorces are exhausted. Both
exceptions will have to be reliably throwable.

Ross Smith

unread,
Aug 16, 2017, 5:11:52 PM8/16/17
to std-dis...@isocpp.org
On 2017-08-16 21:03, Ville Voutilainen wrote:
>
> There is no such spot. Throwing an exception during unwinding calls
> terminate, but
> failing to allocate memory when an exception is initialized before
> it's thrown does not.

Failing to allocate while constructing an exception is just an ordinary
allocation failure, and would be expected to throw bad_alloc just as it
would in any other circumstances. The fact that it happened inside a
throw expression isn't relevant; execution never got as far as actually
attempting to throw the original exception.

It's not clear to me what, if anything, the standard mandates if
allocation fails while copying the exception in the course of throwing
it - I'm pretty sure this would trigger terminate() but I couldn't find
the specifics in the standard. However, it does mandate (21.8.2) that
all standard exception classes have non-throwing copy constructors and
assignment operators, so this is a problem that could only arise with
custom exceptions. Writing your exceptions so that copying them can't
throw is generally considered good practice.

The standard also requires (21.6.3.1) that all operations on bad_alloc,
including default construction, can't throw. So this whole discussion
seems to me to be based on a fundamentally wrong premise. Failing to
allocate while constructing an exception throws bad_alloc, nothing
undefined or ambiguous about that; failing to allocate (or any other
exception-worthy error condition) while throwing bad_alloc can't happen.

Ross Smith

Nevin Liber

unread,
Aug 16, 2017, 5:32:13 PM8/16/17
to std-dis...@isocpp.org
On Wed, Aug 16, 2017 at 4:11 PM, Ross Smith <ross....@otoy.com> wrote:
It's not clear to me what, if anything, the standard mandates if allocation fails while copying the exception in the course of throwing it - I'm pretty sure this would trigger terminate() but I couldn't find the specifics in the standard. However, it does mandate (21.8.2) that all standard exception classes have non-throwing copy constructors and assignment operators, so this is a problem that could only arise with custom exceptions.

That isn't true.  For instance, std::runtime_error usually contains a string whose copy constructor can throw.
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com>  +1-847-691-1404

T. C.

unread,
Aug 16, 2017, 5:43:40 PM8/16/17
to ISO C++ Standard - Discussion
That's why std::runtime_error is typically implemented as holding a reference-counted immutable string so that its copy constructor doesn't actually copy the string.

Nevin Liber

unread,
Aug 16, 2017, 5:58:46 PM8/16/17
to std-dis...@isocpp.org
I'm still not seeing where that is required.

T. C.

unread,
Aug 16, 2017, 6:03:31 PM8/16/17
to ISO C++ Standard - Discussion
[exception]/2, which is cited in the email you quoted.

Each standard library class T that derives from class exception shall have a publicly accessible copy constructor and a publicly accessible copy assignment operator that do not exit with an exception.
 

Michael Kilburn

unread,
Aug 16, 2017, 6:21:24 PM8/16/17
to ISO C++ Standard - Discussion


On Wednesday, August 16, 2017 at 4:02:03 AM UTC-5, Ville Voutilainen wrote:
On 16 August 2017 at 10:05, Michael Kilburn <crusad...@gmail.com> wrote:
> I think you confuse my problem with another one. What I am saying is that
> throwing any exception can have unspecified behavior under low memory
> conditions. 'Unspecified' means standard doesn't mandate certain outcome and
> leaves it to implementation. Most popular ones -- call std::terminate. This
> means I can't write a (portable) program that reliably handles out-of-memory
> condition.

Your earlier example runs out of stack space. See how it behaves if
dynamic allocation is
done instead:
https://wandbox.org/permlink/wLk01q0LnzEdFJvS

Your example throws pointer to an object -- and therefore succeeds. Here is how running of stack space looks like, notice the difference in output from my original example:

 
The standard can't mandate that constructing an exception cannot
consume stack space.
You can't avoid stack exhaustion in a portable manner, because
detecting it and recovering from it
has a cost that some users would not want to pay, and might not even
be possible on some platforms.

>
> Activating buffer allocation doesn't help really -- first of all it is not
> portable, second -- you still end up with limited resource that can run out.
> Instead of trying to play race with environment, I'd rather have a reliable
> way to handle this situation in the code.
>
> Does it make sense?

Your problem has nothing particular to do with exceptions.

Yes, it does.
 

Michael Kilburn

unread,
Aug 16, 2017, 6:25:19 PM8/16/17
to ISO C++ Standard - Discussion
Regardless of these points -- there is a large class of systems where OS won't kill your process because there are no memory left. Process gets NULL from related malloc and it is up to the process to handle it. I want to be able to handle it in some other way than calling std::terminate().

Same for a situation where I (user) explicitly (for whatever reason) want to limit my process to lets say 1Mb of footprint. I want this process to behave when it hit this limit, not crash. And I want that behavior to be robust.

Michael Kilburn

unread,
Aug 16, 2017, 6:28:47 PM8/16/17
to ISO C++ Standard - Discussion


On Wednesday, August 16, 2017 at 1:37:24 AM UTC-5, Thiago Macieira wrote:
On Tuesday, 15 August 2017 18:33:43 PDT Michael Kilburn wrote:
>         Leaving this case unspecified prevents me from writing portable
> code that handles OOM.

You can't portably handle OOM anyway.

This is one of the possible answers, yes. But I would like to understand why I can do it in C, but can not do it in C++.

 
On a modern, multitasking operating system with virtual memory, OOM is
signalled by killing some processes. You don't get notified by it. Your memory
allocations succeed, but you may get SIGBUS when you try to access it.

This contradicts guarantee that C++ gives wrt 'new' -- if it succeeds, I should be able to access memory it allocated.

Michael Kilburn

unread,
Aug 16, 2017, 6:37:10 PM8/16/17
to ISO C++ Standard - Discussion, ross....@otoy.com


On Wednesday, August 16, 2017 at 4:11:52 PM UTC-5, Ross Smith wrote:
On 2017-08-16 21:03, Ville Voutilainen wrote:
>
> There is no such spot. Throwing an exception during unwinding calls
> terminate, but
> failing to allocate memory when an exception is initialized before
> it's thrown does not.

Failing to allocate while constructing an exception is just an ordinary
allocation failure, and would be expected to throw bad_alloc just as it
would in any other circumstances. The fact that it happened inside a
throw expression isn't relevant; execution never got as far as actually
attempting to throw the original exception.

Not that I am against this notion, but where standard says that allocation in every circumstance must produce std::bad_alloc? On the other hand 15.1.p4 clearly states that allocation mechanism is unspecified.

 
It's not clear to me what, if anything, the standard mandates if
allocation fails while copying the exception in the course of throwing
it - I'm pretty sure this would trigger terminate() but I couldn't find
the specifics in the standard. However, it does mandate (21.8.2) that
all standard exception classes have non-throwing copy constructors and
assignment operators, so this is a problem that could only arise with
custom exceptions. Writing your exceptions so that copying them can't
throw is generally considered good practice.

The standard also requires (21.6.3.1) that all operations on bad_alloc,
including default construction, can't throw.

throw can fail during allocation, before construction.

Michael Kilburn

unread,
Aug 16, 2017, 6:46:37 PM8/16/17
to ISO C++ Standard - Discussion
I agree with everything until this point with one note -- as a user I don't care if std::bad_alloc is thrown in both cases -- all I need is a sensible 'backout' mechanism that would allow my application to survive. I don't really need to distinguish between these two situations, but it might be a bonus.
 

If you have an exception failure, you can no longer reliably throw exceptions.

This is correct only on assumption that throwing std::bad_alloc (or std::cant_throw) consumes some limited resource. This can be avoided. Here is a very trivialized example:

    // takes pointer to constructed exception object
    // if NULL -- this means we are processing std::bad_alloc
    void locate_catch_clause_and_unwind_stack(void* exception_ptr);

    void throw_impl(exception_type* p)
    {
        void* xp = alloc_from_exception_heap(p->size);    // return NULL if we ran out of magic
        locate_catch_clause_and_unwind_stack(xp);

Thiago Macieira

unread,
Aug 16, 2017, 6:51:37 PM8/16/17
to std-dis...@isocpp.org
On Wednesday, 16 August 2017 09:26:45 PDT Andrey Semashev wrote:
> >> I think the language has to mandate some sort of escape latch for
> >> std::bad_alloc, even if it means special casing this particular
> >> exception in some ways. Although I realize that that would cause ABI
> >> breakage and therefore would not be an easy change.
> >
> > I agree that the language should mandate some sort of escape latch, but I
> > think it should be std::terminate. That's how we deal with double faults
> > elsewhere and this is a double fault.
>
> std::terminate leaves no room for recovery, that's my main problem with
> it. And the user's code may be written in a way that makes recovery
> possible, if only in majority of cases.

But you shouldn't try and recover. If you can t properly report your failure
condition, then you can't recover from it. Changing the exception type to
std::bad_alloc may cause further problems up the stack.

If you have a double fault, std::terminate is the only thing you can do.

> Stack overflow I tend to consider as a result of misbehavior as well, as
> it is unlikely to be a result of a well behaved program, but I could
> probably be convinced otherwise on this particular case. In any case,
> stack overflow is a separate problem that is not related to exceptions,
> and it may have a different solution. For example, the language could
> introduce another exception for this purpose, and the implementation
> would be bound to always reserve enough stack space to be able to throw
> this exception.

I don't agree. A well-behaved program could very well exhaust stack space
without causing an infinite recursion. You may call it poorly-written to
allocate gobs of memory on the stack, but that's still well-behaved in other
conditions.

Also, the system configuration may have lowered the stack size for some reason.
So your program that is well-behaved somewhere isn't elsewhere.

> > In other words, we need an escape latch for the escape latch (an exception
> > to the exception to throwing exceptions) which is back again to allowed
> > to fail.
> I don't think so. For example, allowing the implementation to throw
> shared pre-allocated instance of std::bad_alloc might be the solution to
> the particular problem with exceptions. The other problems you mentioned
> either don't need to be solved or can have different solutions.

Call std::terminate. It's double fault.

Michael Kilburn

unread,
Aug 16, 2017, 6:57:08 PM8/16/17
to ISO C++ Standard - Discussion


On Wednesday, August 16, 2017 at 10:32:39 AM UTC-5, Thiago Macieira wrote:
On Tuesday, 15 August 2017 23:55:21 PDT Michael Kilburn wrote:
> > > "throw std::bad_alloc() should never fail" requirement from compiler
> > > standpoint.
> >
> > That wouldn't solve your problem.
>
> Why do you think so?

Because you said your problem is that you wanted to throw something else, an
exception class possibly much bigger than std::bad_alloc. So even if the
implementation is required to be able to throw std::bad_alloc, it won't be
required to always be able to throw your type.

No, I said my problem is that my application terminates even though it is 100% correct. And that requirement (along with "if throw fails to allocate memory for exception -- throw bad_alloc" requirement) certainly would solve my problem . That may make other things too expensive (or simply not possible to implement) -- then I'd like to understand how and why.

Ross Smith

unread,
Aug 16, 2017, 6:59:15 PM8/16/17
to std-dis...@isocpp.org
On 2017-08-17 10:37, Michael Kilburn wrote:
>
> On Wednesday, August 16, 2017 at 4:11:52 PM UTC-5, Ross Smith wrote:
>
> On 2017-08-16 21:03, Ville Voutilainen wrote:
> >
> > There is no such spot. Throwing an exception during unwinding calls
> > terminate, but
> > failing to allocate memory when an exception is initialized before
> > it's thrown does not.
>
> Failing to allocate while constructing an exception is just an ordinary
> allocation failure, and would be expected to throw bad_alloc just as it
> would in any other circumstances. The fact that it happened inside a
> throw expression isn't relevant; execution never got as far as actually
> attempting to throw the original exception.
>
> Not that I am against this notion, but where standard says that
> allocation in every circumstance must produce std::bad_alloc? On the
> other hand 15.1.p4 clearly states that allocation mechanism is unspecified.

I'm no sure which part of the standard you're referring to there; I
can't see anything relevant in 15.1/4 in the current draft. Probably my
fault for using section numbers in the first place, we should use the
labels. Anyway, [bad.alloc] (21.6.3.1 in the current draft) seems pretty
clear: "The class bad_alloc defines the type of objects thrown as
exceptions by the implementation to report a failure to allocate storage."

> The standard also requires (21.6.3.1) that all operations on bad_alloc,
> including default construction, can't throw.
>
> throw can fail during allocation, before construction.

I have no idea what you mean by that. Constructing and throwing
bad_alloc doesn't involve any allocation; constructing some other
exception type can throw, but that's just an ordinary allocation failure
and throws bad_alloc as usual, no special rules required.

(Well, it's not quite true that bad_alloc can't allocate anything in its
constructor: if it does, it has to catch and handle any failure
internally, without throwing. I can imagine an implementation where
bad_alloc's constructor tries to construct a string with a detailed
error message, but if that fails, gives up and just holds a pointer to a
static string with a generic message.)

Ross Smith

Thiago Macieira

unread,
Aug 16, 2017, 7:00:56 PM8/16/17
to std-dis...@isocpp.org
On Wednesday, 16 August 2017 15:46:37 PDT Michael Kilburn wrote:
> I agree with everything until this point with one note -- as a user I don't
> care if std::bad_alloc is thrown in both cases -- all I need is a sensible
> 'backout' mechanism that would allow my application to survive. I don't
> really need to distinguish between these two situations, but it might be a
> bonus.

Explain to me what you're going to do if you can't free any more memory. That
is, what happens if the OOM situation is caused by another program on the same
machine.

> > If you have an exception failure, you can no longer reliably throw
> > exceptions.
>
> This is correct only on assumption that throwing std::bad_alloc (or
> std::cant_throw) consumes some limited resource. This can be avoided. Here
> is a very trivialized example:
>
> // takes pointer to constructed exception object
> // if NULL -- this means we are processing std::bad_alloc
> void locate_catch_clause_and_unwind_stack(void* exception_ptr);
>
> void throw_impl(exception_type* p)
> {
> void* xp = alloc_from_exception_heap(p->size); // return NULL if
> we ran out of magic
> locate_catch_clause_and_unwind_stack(xp);
> }

You're assuming that "locate_catch_clause_and_unwind_stack" can operate
without allocating memory too. The act of unwinding may require resources too.
That means either dynamic (which can fail) or static (which we can exhaust).

Thiago Macieira

unread,
Aug 16, 2017, 7:02:58 PM8/16/17
to std-dis...@isocpp.org
On Wednesday, 16 August 2017 15:25:19 PDT Michael Kilburn wrote:
> Regardless of these points -- there is a large class of systems where OS
> won't kill your process because there are no memory left. Process gets NULL
> from related malloc and it is up to the process to handle it. I want to be
> able to handle it in some other way than calling std::terminate().
>
> Same for a situation where I (user) explicitly (for whatever reason) want
> to limit my process to lets say 1Mb of footprint. I want this process to
> behave when it hit this limit, not crash. And I want that behavior to be
> robust.

Then you need to talk to those system vendors and ask them to provide such
robustness.

You were asking for portability. You can't portably handle OOM, despite what
the C++ standard may say.

Thiago Macieira

unread,
Aug 16, 2017, 7:04:46 PM8/16/17
to std-dis...@isocpp.org
On Wednesday, 16 August 2017 15:28:46 PDT Michael Kilburn wrote:
> On Wednesday, August 16, 2017 at 1:37:24 AM UTC-5, Thiago Macieira wrote:
> > You can't portably handle OOM anyway.
>
> This is one of the possible answers, yes. But I would like to understand
> why I can do it in C, but can not do it in C++.

C has the same problems I listed.

It's just that C++ introduces one more: its failure reporting mechanism can
itself fail.

> > On a modern, multitasking operating system with virtual memory, OOM is
> > signalled by killing some processes. You don't get notified by it. Your
> > memory
> > allocations succeed, but you may get SIGBUS when you try to access it.
>
> This contradicts guarantee that C++ gives wrt 'new' -- if it succeeds, I
> should be able to access memory it allocated.

It does. Nothing we can do about it.

Michael Kilburn

unread,
Aug 16, 2017, 7:07:25 PM8/16/17
to ISO C++ Standard - Discussion, ross....@otoy.com


On Wednesday, August 16, 2017 at 5:59:15 PM UTC-5, Ross Smith wrote:
On 2017-08-17 10:37, Michael Kilburn wrote:
>
> On Wednesday, August 16, 2017 at 4:11:52 PM UTC-5, Ross Smith wrote:
>
>     On 2017-08-16 21:03, Ville Voutilainen wrote:
>      >
>      > There is no such spot. Throwing an exception during unwinding calls
>      > terminate, but
>      > failing to allocate memory when an exception is initialized before
>      > it's thrown does not.
>
>     Failing to allocate while constructing an exception is just an ordinary
>     allocation failure, and would be expected to throw bad_alloc just as it
>     would in any other circumstances. The fact that it happened inside a
>     throw expression isn't relevant; execution never got as far as actually
>     attempting to throw the original exception.
>
> Not that I am against this notion, but where standard says that
> allocation in every circumstance must produce std::bad_alloc? On the
> other hand 15.1.p4 clearly states that allocation mechanism is unspecified.

I'm no sure which part of the standard you're referring to there; I
can't see anything relevant in 15.1/4 in the current draft. Probably my
fault for using section numbers in the first place, we should use the
labels.

Quote:
C++11 15.1/4:
The memory for the exception object is allocated in an unspecified way, except as noted in 3.7.4.1.

 
Anyway, [bad.alloc] (21.6.3.1 in the current draft) seems pretty
clear: "The class bad_alloc defines the type of objects thrown as
exceptions by the implementation to report a failure to allocate storage."

It doesn't say this has to be used for every allocation.

 
>     The standard also requires (21.6.3.1) that all operations on bad_alloc,
>     including default construction, can't throw.
>
> throw can fail during allocation, before construction.

I have no idea what you mean by that. Constructing and throwing
bad_alloc doesn't involve any allocation; constructing some other
exception type can throw, but that's just an ordinary allocation failure
and throws bad_alloc as usual, no special rules required.

For every value to be constructed you need to allocate memory for it first. Doesn't matter if it is local storage (stack), global or dynamic. In terms of implementation allocation can mean as little as adding 4 to some "stack pointer(SP)" register or calling malloc().

For exception to be constructed compiler has to decided where it will be constructed -- this is allocation I was talking about. And 15.1/4 seems to deliberately avoid putting any requirements on this part. I imagine to allow both MSVC (where exception gets constructed on the stack) and GCC (heap) implementations.

 
(Well, it's not quite true that bad_alloc can't allocate anything in its
constructor: if it does, it has to catch and handle any failure
internally, without throwing. I can imagine an implementation where
bad_alloc's constructor tries to construct a string with a detailed
error message, but if that fails, gives up and just holds a pointer to a
static string with a generic message.)

What happens during exception object construction is irrelevant to original problem.
 

Ross Smith

Michael Kilburn

unread,
Aug 16, 2017, 7:12:18 PM8/16/17
to ISO C++ Standard - Discussion


On Wednesday, August 16, 2017 at 6:02:58 PM UTC-5, Thiago Macieira wrote:
On Wednesday, 16 August 2017 15:25:19 PDT Michael Kilburn wrote:
> Regardless of these points -- there is a large class of systems where OS
> won't kill your process because there are no memory left. Process gets NULL
> from related malloc and it is up to the process to handle it. I want to be
> able to handle it in some other way than calling std::terminate().
>
> Same for a situation where I (user) explicitly (for whatever reason) want
> to limit my process to lets say 1Mb of footprint. I want this process to
> behave when it hit this limit, not crash. And I want that behavior to be
> robust.

Then you need to talk to those system vendors and ask them to provide such
robustness.

I prefer to get this guarantee from C++ itself -- it will give me portability. Especially, considering that I can do it in C and (so far) don't see why C++ can't do the same.
 

You were asking for portability. You can't portably handle OOM, despite what
the C++ standard may say.

If given system doesn't comply with requirements outlined in C++ standard -- I seriously don't care about them. I write C++ code and I expect it to be executed only on compliant systems.

Thiago Macieira

unread,
Aug 16, 2017, 7:15:38 PM8/16/17
to std-dis...@isocpp.org
On Wednesday, 16 August 2017 16:12:17 PDT Michael Kilburn wrote:
> If given system doesn't comply with requirements outlined in C++ standard
> -- I seriously don't care about them. I write C++ code and I expect it to
> be executed only on compliant systems.

You don't have to care. That's your choice.

Just note you'll be excluding Linux, FreeBSD, macOS and Windows.

Michael Kilburn

unread,
Aug 16, 2017, 7:16:32 PM8/16/17
to ISO C++ Standard - Discussion


On Wednesday, August 16, 2017 at 6:00:56 PM UTC-5, Thiago Macieira wrote:
On Wednesday, 16 August 2017 15:46:37 PDT Michael Kilburn wrote:
> I agree with everything until this point with one note -- as a user I don't
> care if std::bad_alloc is thrown in both cases -- all I need is a sensible
> 'backout' mechanism that would allow my application to survive. I don't
> really need to distinguish between these two situations, but it might be a
> bonus.

Explain to me what you're going to do if you can't free any more memory. That
is, what happens if the OOM situation is caused by another program on the same
machine.

It depends on application. My favorite example for this situation is a server that accepts connections (creates a thread for each one), reads data (user commands), executes them and in case of exception -- everything in given thread unwinds, connection gets closed and server survives (along with other connections). 


 > > If you have an exception failure, you can no longer reliably throw 
> > exceptions.
>
> This is correct only on assumption that throwing std::bad_alloc (or
> std::cant_throw) consumes some limited resource. This can be avoided. Here
> is a very trivialized example:
>
>     // takes pointer to constructed exception object
>     // if NULL -- this means we are processing std::bad_alloc
>     void locate_catch_clause_and_unwind_stack(void* exception_ptr);
>
>     void throw_impl(exception_type* p)
>     {
>         void* xp = alloc_from_exception_heap(p->size);    // return NULL if
> we ran out of magic
>         locate_catch_clause_and_unwind_stack(xp);
>     }

You're assuming that "locate_catch_clause_and_unwind_stack" can operate
without allocating memory too. The act of unwinding may require resources too.
That means either dynamic (which can fail) or static (which we can exhaust).

I am pretty sure current implementations don't allocate additional memory besides extra stack space -- but this problem exists in C too and crash on stack overflow is an long accepted behaviour.

Michael Kilburn

unread,
Aug 16, 2017, 7:18:40 PM8/16/17
to ISO C++ Standard - Discussion


On Wednesday, August 16, 2017 at 6:15:38 PM UTC-5, Thiago Macieira wrote:
On Wednesday, 16 August 2017 16:12:17 PDT Michael Kilburn wrote:
> If given system doesn't comply with requirements outlined in C++ standard
> -- I seriously don't care about them. I write C++ code and I expect it to
> be executed only on compliant systems.

You don't have to care. That's your choice.

Just note you'll be excluding Linux, FreeBSD, macOS and Windows.

I don't understand how this remark contributes anything to this discussion. Also, I am pretty sure all these systems are considered compliant with current C++ requirements.


Thiago Macieira

unread,
Aug 16, 2017, 7:32:24 PM8/16/17
to std-dis...@isocpp.org
Except for the part you quoted elsewhere in this thread: that if new succeeds,
you can access those bytes without problem. That's not the case: new may
succeed and you may still SIGBUS when accessing those bytes.

Thiago Macieira

unread,
Aug 16, 2017, 7:40:10 PM8/16/17
to std-dis...@isocpp.org
On Wednesday, 16 August 2017 16:16:32 PDT Michael Kilburn wrote:
> > Explain to me what you're going to do if you can't free any more memory.
> > That
> > is, what happens if the OOM situation is caused by another program on the
> > same
> > machine.
>
> It depends on application. My favorite example for this situation is a
> server that accepts connections (creates a thread for each one), reads data
> (user commands), executes them and in case of exception -- everything in
> given thread unwinds, connection gets closed and server survives (along
> with other connections).

Ok, suppose the connection is active and you're about to send a packet. In the
calculation for that packet, some operation wants to throw and fails. That
means you're going to get a stack unwinding.

At what point do you catch it? What memory will you free? If you're going to
close the socket, are you going to try and send a clean shutdown? That may
need to allocate memory again.

Do you have any other recourse besides closing the socket uncleanly and
exiting the thread? And how much memory will that free?

> > > is a very trivialized example:
> > > // takes pointer to constructed exception object
> > > // if NULL -- this means we are processing std::bad_alloc
> > > void locate_catch_clause_and_unwind_stack(void* exception_ptr);
> > >
> > > void throw_impl(exception_type* p)
> > > {
> > >
> > > void* xp = alloc_from_exception_heap(p->size); // return NULL
> >
> > if
> >
> > > we ran out of magic
> > >
> > > locate_catch_clause_and_unwind_stack(xp);
> > >
> > > }
> >
> > You're assuming that "locate_catch_clause_and_unwind_stack" can operate
> > without allocating memory too. The act of unwinding may require resources
> > too.
> > That means either dynamic (which can fail) or static (which we can
> > exhaust).
>
> I am pretty sure current implementations don't allocate additional memory
> besides extra stack space -- but this problem exists in C too and crash on
> stack overflow is an long accepted behaviour.

I've never understood how you can allocate stack space while unwinding the
stack. During the unwind process, the stack pointers need to be adjusted back
to where they used to be. On Linux/x86, once you adjust the SP register, any
data below the pointer becomes garbage and no longer guaranteed to be retained
(or more than 128 bytes below it, or some other limit).

That means any resources the unwinder may need to preserve during the
unwinding need to be somewhere else other than the stack.

Michael Kilburn

unread,
Aug 16, 2017, 7:42:47 PM8/16/17
to ISO C++ Standard - Discussion


On Wednesday, August 16, 2017 at 6:32:24 PM UTC-5, Thiago Macieira wrote:
On Wednesday, 16 August 2017 16:18:40 PDT Michael Kilburn wrote:
> On Wednesday, August 16, 2017 at 6:15:38 PM UTC-5, Thiago Macieira wrote:
> > On Wednesday, 16 August 2017 16:12:17 PDT Michael Kilburn wrote:
> > > If given system doesn't comply with requirements outlined in C++
> >
> > standard
> >
> > > -- I seriously don't care about them. I write C++ code and I expect it
> >
> > to
> >
> > > be executed only on compliant systems.
> >
> > You don't have to care. That's your choice.
> >
> > Just note you'll be excluding Linux, FreeBSD, macOS and Windows.
>
> I don't understand how this remark contributes anything to this discussion.
> Also, I am pretty sure all these systems are considered compliant with
> current C++ requirements.

Except for the part you quoted elsewhere in this thread: that if new succeeds,
you can access those bytes without problem. That's not the case: new may
succeed and you may still SIGBUS when accessing those bytes.

I am pretty sure you are wrong about this one. I am coding for more than 20 years -- never heard of this. I would appreciate if you provide some information about this particular case.

Michael Kilburn

unread,
Aug 16, 2017, 7:55:07 PM8/16/17
to ISO C++ Standard - Discussion


On Wednesday, August 16, 2017 at 6:40:10 PM UTC-5, Thiago Macieira wrote:
On Wednesday, 16 August 2017 16:16:32 PDT Michael Kilburn wrote:
> > Explain to me what you're going to do if you can't free any more memory.
> > That
> > is, what happens if the OOM situation is caused by another program on the
> > same
> > machine.
>
> It depends on application. My favorite example for this situation is a
> server that accepts connections (creates a thread for each one), reads data
> (user commands), executes them and in case of exception -- everything in
> given thread unwinds, connection gets closed and server survives (along
> with other connections).

Ok, suppose the connection is active and you're about to send a packet. In the
calculation for that packet, some operation wants to throw and fails. That
means you're going to get a stack unwinding.

At what point do you catch it?

I'll catch it at the root of thread stack, send note to client (smth like "error: std::bad_alloc"), close connection and exit (which in turn kill the thread and deallocate it's stack)

 
What memory will you free?

I will free all memory allocated in operations generated by this connection.
 
If you're going to close the socket, are you going to try and send a clean shutdown?

On my side error string will be preallocated (or I will fallback to static string). On OS side `send()` will fail and I close socket in a way that will signal user that smth went wrong (if possible).

 
Do you have any other recourse besides closing the socket uncleanly and
exiting the thread? And how much memory will that free?

It will free exactly as much memory as given thread allocated. Server will stay up and all it's other users will continue whatever they are doing. And if OS suddenly discover more memory (some other process exited) -- it will be transparently utilized when serving subsequent requests.

 
> > > is a very trivialized example:
> > >     // takes pointer to constructed exception object
> > >     // if NULL -- this means we are processing std::bad_alloc
> > >     void locate_catch_clause_and_unwind_stack(void* exception_ptr);
> > >    
> > >     void throw_impl(exception_type* p)
> > >     {
> > >    
> > >         void* xp = alloc_from_exception_heap(p->size);    // return NULL
> >
> > if
> >
> > > we ran out of magic
> > >
> > >         locate_catch_clause_and_unwind_stack(xp);
> > >    
> > >     }
> >
> > You're assuming that "locate_catch_clause_and_unwind_stack" can operate
> > without allocating memory too. The act of unwinding may require resources
> > too.
> > That means either dynamic (which can fail) or static (which we can
> > exhaust).
>
> I am pretty sure current implementations don't allocate additional memory
> besides extra stack space -- but this problem exists in C too and crash on
> stack overflow is an long accepted behaviour.

I've never understood how you can allocate stack space while unwinding the
stack. During the unwind process, the stack pointers need to be adjusted back
to where they used to be. On Linux/x86, once you adjust the SP register, any
data below the pointer becomes garbage and no longer guaranteed to be retained
(or more than 128 bytes below it, or some other limit).

As far as I know MSVC (during unwinding) goes up the stack and calls destructors using current "remainder" of stack. Once unwinding is done, it executes catch() clause in similar way and adjusts stack related registers (this "freeing" potentially large portion of stack).

Not sure about GCC, but I imagine it does something similar.

 
That means any resources the unwinder may need to preserve during the
unwinding need to be somewhere else other than the stack.

This is assuming unwinder needs any resources (besides more stack). For example in case of table-driven unwinding you basically end up searching and traversing a tree stored in static data (loaded from disk as part of your executable) -- you don't need extra memory for that. In old days MSVC was walking stack frames and looking for data it knew should be there. Not sure what it is doing nowadays.

Patrice Roy

unread,
Aug 16, 2017, 7:56:33 PM8/16/17
to std-dis...@isocpp.org
Michael : how about writing a paper recommending that implementations always ensure there's at least enough resources available to throw std::bad_alloc? You might encounter resistance from embedded systems developers, but at least it would be discussed by the committee.

Just a thought :)

--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+unsubscribe@isocpp.org.
To post to this group, send email to std-dis...@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.

Michael Kilburn

unread,
Aug 16, 2017, 8:08:33 PM8/16/17
to ISO C++ Standard - Discussion


On Wednesday, August 16, 2017 at 6:56:33 PM UTC-5, Patrice Roy wrote:
Michael : how about writing a paper recommending that implementations always ensure there's at least enough resources available to throw std::bad_alloc? You might encounter resistance from embedded systems developers, but at least it would be discussed by the committee.

Just a thought :)

:)

I know there is an intent behind every rule standard puts forth. I'd like to understand the intent (and reasons) behind 15.1/4 and why it leaves behavior unspecified. Don't want to raise questions that were probably answered long ago.

If it turns out to be an oversight (or oversimplification) that might be 'tightened' a bit without breaking too many things -- then yes, I would really like this to be done.

Not sure about writing my own paper about it and driving related process -- I have no idea how it works. And frankly -- I'd rather be coding than dealing with bureaucracy usually associated with these processes. :-)

 
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussio...@isocpp.org.

Nicol Bolas

unread,
Aug 16, 2017, 8:10:32 PM8/16/17
to ISO C++ Standard - Discussion, ross....@otoy.com


On Wednesday, August 16, 2017 at 5:11:52 PM UTC-4, Ross Smith wrote:
On 2017-08-16 21:03, Ville Voutilainen wrote:
>
> There is no such spot. Throwing an exception during unwinding calls
> terminate, but
> failing to allocate memory when an exception is initialized before
> it's thrown does not.

Failing to allocate while constructing an exception is just an ordinary
allocation failure, and would be expected to throw bad_alloc just as it
would in any other circumstances.

I think you're misunderstanding the use case here. This is not failing to allocate "while constructing an exception". This is failing to allocate the storage for the exception object itself. This is not about what goes on in a constructor.

When you do `throw expr;`, the first step is the evaluation of `expr` into a value. Then the compiler allocates storage for the exception object, which is of the same type as `expr`. The compiler then copies/moves from the `expr` evaluated value into that storage.

We're talking about the failure of step 2. It has nothing to do with user-defined code. It's more like allocating additional pages of stack memory; it's an implementation detail which causes the program to die if it is ever unable to be accomplished.
 
The fact that it happened inside a
throw expression isn't relevant; execution never got as far as actually
attempting to throw the original exception.

Creating the exception object is the beginning of the process of throwing an exception. Everything after the evaluation of the throw expression is part of exception handling. That's why the copy/move constructor of an exception isn't allowed to throw, and that's why `throw` statements aren't allowed to participate in guaranteed elision.
 
It's not clear to me what, if anything, the standard mandates if
allocation fails while copying the exception in the course of throwing
it - I'm pretty sure this would trigger terminate() but I couldn't find
the specifics in the standard.

It's in [except.throw]/7:

> If the exception handling mechanism handling an uncaught exception (18.5.2) directly invokes a function that exits via an exception, std::terminate is called (18.5.1).

And the example attached specifically calls out the copy constructor of the exception type:

struct C {
  C
() { }
  C
(const C&) {
   
if (std::uncaught_exceptions()) {
     
throw 0; // throw during copy to handler’s exception-declaration object (18.3)
   
}
 
}
};

int main() {
 
try {
   
throw C(); // calls std::terminate() if construction of the handler’s
               
// exception-declaration object is not elided (15.8)
 
} catch(C) { }
}

All that being said... I'm not as sure I'm right as I was when I started writing that. The reason? The line cites 18.5.2, which states:

> An exception is considered uncaught after completing the initialization of the exception object

That seems to suggest that, in the above code, the exception isn't uncaught when the `throw` in `C`'s copy constructor is encountered. Therefore, `std::uncaught_exceptions()` should be 0.

This looks like a genuine defect in the standard.

Thiago Macieira

unread,
Aug 16, 2017, 8:32:13 PM8/16/17
to std-dis...@isocpp.org
On Wednesday, 16 August 2017 16:55:07 PDT Michael Kilburn wrote:
> > If you're going to close the socket, are you going to try and send a clean
> > shutdown?
>
> On my side error string will be preallocated (or I will fallback to static
> string). On OS side `send()` will fail and I close socket in a way that
> will signal user that smth went wrong (if possible).

If it's a UDP socket, closing it will not send anything to the other side. The
other side will only timeout.

If you preallocate memory for the OOM condition, you may cause the OOM
condition you were trying to avoid.

That reminds me of developing for Symbian, where a 32 MB VRAM system reserved
1 MB for displaying the OOM message. When the camera app was running, it
consumed 30 MB VRAM. That meant everything else needed to work with just 1
MB...

> > I've never understood how you can allocate stack space while unwinding the
> > stack. During the unwind process, the stack pointers need to be adjusted
> > back
> > to where they used to be. On Linux/x86, once you adjust the SP register,
> > any
> > data below the pointer becomes garbage and no longer guaranteed to be
> > retained
> > (or more than 128 bytes below it, or some other limit).
>
> As far as I know MSVC (during unwinding) goes up the stack and calls
> destructors using current "remainder" of stack. Once unwinding is done, it
> executes catch() clause in similar way and adjusts stack related registers
> (this "freeing" potentially large portion of stack).
>
> Not sure about GCC, but I imagine it does something similar.

It does not.

The problem is where it can store the information it needs to save while those
destructors are running. It can't be below the stack, since the destructors
may call other functions that consume stack space. Even if they didn't, Unix
signals may be delivered and those also consume stack space.

Since it's not below the stack, it needs to be either in the heap or above the
stack (thread-specific reserved area).

> > That means any resources the unwinder may need to preserve during the
> > unwinding need to be somewhere else other than the stack.
>
> This is assuming unwinder needs any resources (besides more stack). For
> example in case of table-driven unwinding you basically end up searching
> and traversing a tree stored in static data (loaded from disk as part of
> your executable) -- you don't need extra memory for that. In old days MSVC
> was walking stack frames and looking for data it knew should be there. Not
> sure what it is doing nowadays.

Great if it can be implemented without consuming more resources. But that's
not a given: on some ABI, allocating and keeping memory during unwinding may
be necessary. And that's aside from the exception object itself, which is most
definitely not stored in a static table.

If there are ABIs like that and C++ starts requiring them to change, it would
be a huge breakage for existing users. Big enough that I can predict
objections strong enough to the C++ Standard change that would back it out.

So, no, you have to cope with the fact that throwing exceptions may fail, even
for the smallest and/or pre-defined types. And it may be that the systems thus
affected are mainstream.

T. C.

unread,
Aug 16, 2017, 8:33:51 PM8/16/17
to ISO C++ Standard - Discussion, ross....@otoy.com


On Wednesday, August 16, 2017 at 8:10:32 PM UTC-4, Nicol Bolas wrote:
And the example attached specifically calls out the copy constructor of the exception type:

struct C {
  C
() { }
  C
(const C&) {
   
if (std::uncaught_exceptions()) {
     
throw 0; // throw during copy to handler’s exception-declaration object (18.3)
   
}
 
}
};

int main() {
 
try {
   
throw C(); // calls std::terminate() if construction of the handler’s
               
// exception-declaration object is not elided (15.8)
 
} catch(C) { }
}

All that being said... I'm not as sure I'm right as I was when I started writing that. The reason? The line cites 18.5.2, which states:

> An exception is considered uncaught after completing the initialization of the exception object

That seems to suggest that, in the above code, the exception isn't uncaught when the `throw` in `C`'s copy constructor is encountered. Therefore, `std::uncaught_exceptions()` should be 0.

This looks like a genuine defect in the standard.

The terminate-causing throw in that example isn't in the initialization of the exception object; it's in the initialization of the by-value parameter of the catch. The handler isn't active, and the exception isn't caught, until that initialization is complete.

Nicol Bolas

unread,
Aug 16, 2017, 8:36:43 PM8/16/17
to ISO C++ Standard - Discussion, ross....@otoy.com

Then the comment is wrong. It should be on the catch statement, not the throw. I double-checked with N4659, and it appears there as I copied it.

T. C.

unread,
Aug 16, 2017, 8:41:00 PM8/16/17
to ISO C++ Standard - Discussion, ross....@otoy.com
Perhaps the placement could use some improvement (but again, the terminate happens after the throw and before the catch, so it's not clear having it on the catch would be better). 

Nonetheless, the comment does say "the handler's exception-declaration object", and there's only one handler and only one exception-declaration in that example.

Thiago Macieira

unread,
Aug 16, 2017, 8:44:32 PM8/16/17
to std-dis...@isocpp.org
On Wednesday, 16 August 2017 16:42:47 PDT Michael Kilburn wrote:
> > Except for the part you quoted elsewhere in this thread: that if new
> > succeeds,
> > you can access those bytes without problem. That's not the case: new may
> > succeed and you may still SIGBUS when accessing those bytes.
>
> I am pretty sure you are wrong about this one. I am coding for more than 20
> years -- never heard of this. I would appreciate if you provide some
> information about this particular case.

I'm not.

I just tried allocating thrice 15 GB on my 16 GB RAM machine (that's three
times in the same process, without freeing the previous block). I have 16 GB
swap too, so even the maximum VM usable is 32 GB, not 45.

std::size_t n = 15ULL*1024*1024*1024;
char *ptr = new char[n];
ptr = new char[n];
ptr = new char[n];

strace shows the kernel did "allocate" memory:

mmap(NULL, 16106131456, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1,
0) = 0x7f2e1ed4b000 <0.000005>
mmap(NULL, 16106131456, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1,
0) = 0x7f2a5ed4a000 <0.000007>
mmap(NULL, 16106131456, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1,
0) = 0x7f269ed49000 <0.000004>

That number in angle brackets is the time in seconds that the system call
took. That is, BOTH 15 GB allocations took 7µs or less to run. Do you think
the kernel really allocated the memory?

No, it recorded that those two regions of memory were valid, that's it. The
pages would be faulted in when you accessed them.

And since there's no way to provide 45 GB of memory, some bytes are not
accessible.

Patrice Roy

unread,
Aug 16, 2017, 9:46:02 PM8/16/17
to std-dis...@isocpp.org
See https://isocpp.org/std/submit-a-proposal and consider you've begun step 1 :)

If you think you have a valid point, you can come to an actual meeting to express it. It's open and costs nothing (apart from such things as food, lodging and travel costs, which are on you; it's all volunteer work). You probably won't be able to vote in plenary, but you'll be able to participate otherwise, express yourself and take part in straw polls.

I think you'll get (quite reasonable) opposition from people working in memory-constrained environments, and from those who do not use exceptions. You'll probably want to explore what ensuring there's enough memory to throw std::bad_alloc at all times means to these groups (in one case, every byte counts; in the other case, using precious memory for an unused feature). It's a matter of tradeoffs, or so I suppose. You'll want to make a convincing argument.

Another avenue to consider is to contact your compiler vendor(s), as for the moment it's an implementation-specific thing. Maybe that would be sufficient (compiler options or somesuch might do it), and it would be less work for you.

Good luck!


To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+unsubscribe@isocpp.org.

Michael Kilburn

unread,
Aug 17, 2017, 12:32:15 AM8/17/17
to ISO C++ Standard - Discussion
Ahh... I see. 'Full overcommit'. Some claim it to be bad practice:

I personally don't care, it is just a tool. If you don't like it -- don't use it. When setting up an environment for my app that is supposed to handle OOMs I'll make sure it is switched off.

In last Linux environment I worked we never had it on -- because OOM-killer always chose the most important app to kill, the one that spent last 14 hours chewing through data that needs to be available before next trading day starts :-)

I don't know if this behavior is allowed by C++ standard. Probably is. If yes -- then you are correct, you can't build portable C or C++ app that can handle OOMs, but it is not the reason to deny this possibility to those systems where OOM-killer isn't used.

Also, this is kinda irrelevant -- my biggest grind with 'crashing throw' can be summed like this:
- exceptions and return codes are essentially the same thing -- it is a way to pass error object up the call stack, just implemented differently
- you can always pass this error object using C technique
- why you can't to it using C++ technique?

Michael Kilburn

unread,
Aug 17, 2017, 12:36:35 AM8/17/17
to ISO C++ Standard - Discussion


On Wednesday, August 16, 2017 at 6:04:46 PM UTC-5, Thiago Macieira wrote:
On Wednesday, 16 August 2017 15:28:46 PDT Michael Kilburn wrote:
> On Wednesday, August 16, 2017 at 1:37:24 AM UTC-5, Thiago Macieira wrote:
> > You can't portably handle OOM anyway.
>
> This is one of the possible answers, yes. But I would like to understand
> why I can do it in C, but can not do it in C++.

C has the same problems I listed.

It's just that C++ introduces one more: its failure reporting mechanism can
itself fail.

Yes, you could sum it up this way... Unfortunately this error handling mechanism is also a default one in C++. Which means it's 'fallibility' affects (infects?) practically everything.

Also it rather convenient. I want to understand why it can't be implemented as no-fail and if it is possible -- fix it.

 
> > On a modern, multitasking operating system with virtual memory, OOM is
> > signalled by killing some processes. You don't get notified by it. Your
> > memory
> > allocations succeed, but you may get SIGBUS when you try to access it.
>
> This contradicts guarantee that C++ gives wrt 'new' -- if it succeeds, I
> should be able to access memory it allocated.

It does. Nothing we can do about it.

Yes, you proved your point (on assumption that standard allows this behavior). In general case. :-) 

Michael Kilburn

unread,
Aug 17, 2017, 12:47:50 AM8/17/17
to ISO C++ Standard - Discussion


On Wednesday, August 16, 2017 at 7:32:13 PM UTC-5, Thiago Macieira wrote:
On Wednesday, 16 August 2017 16:55:07 PDT Michael Kilburn wrote:
> > If you're going to close the socket, are you going to try and send a clean
> > shutdown?
>
> On my side error string will be preallocated (or I will fallback to static
> string). On OS side `send()` will fail and I close socket in a way that
> will signal user that smth went wrong (if possible).

If it's a UDP socket, closing it will not send anything to the other side. The
other side will only timeout.

How your system is designed to behave on a higher level is irrelevant to the objective -- server that handles OOMs without dying. You may rely on keep-alive packets to discover that connection was lost or smth else.

 
If you preallocate memory for the OOM condition, you may cause the OOM
condition you were trying to avoid.

That reminds me of developing for Symbian, where a 32 MB VRAM system reserved
1 MB for displaying the OOM message. When the camera app was running, it
consumed 30 MB VRAM. That meant everything else needed to work with just 1
MB...

It doesn't really matter how much memory you have -- sooner or later 640kb won't be enough. Some people choose cheaper variant (upgrade hardware, process watchers, etc), some people choose code that handles OOM, or combination of both.
 

> > I've never understood how you can allocate stack space while unwinding the
> > stack. During the unwind process, the stack pointers need to be adjusted
> > back
> > to where they used to be. On Linux/x86, once you adjust the SP register,
> > any
> > data below the pointer becomes garbage and no longer guaranteed to be
> > retained
> > (or more than 128 bytes below it, or some other limit).
>
> As far as I know MSVC (during unwinding) goes up the stack and calls
> destructors using current "remainder" of stack. Once unwinding is done, it
> executes catch() clause in similar way and adjusts stack related registers
> (this "freeing" potentially large portion of stack).
>
> Not sure about GCC, but I imagine it does something similar.

It does not.

The problem is where it can store the information it needs to save while those
destructors are running. It can't be below the stack, since the destructors
may call other functions that consume stack space. Even if they didn't, Unix
signals may be delivered and those also consume stack space.

I don't know these details, if I knew -- I would not be asking these questions here. I always assumed that stack unwinding can't fail if destructors don't throw (and there is enough stack left).

Which information needs to be saved 'on-the-side' to run destructors with GCC?

 
Since it's not below the stack, it needs to be either in the heap or above the
stack (thread-specific reserved area).

> > That means any resources the unwinder may need to preserve during the
> > unwinding need to be somewhere else other than the stack.
>
> This is assuming unwinder needs any resources (besides more stack). For
> example in case of table-driven unwinding you basically end up searching
> and traversing a tree stored in static data (loaded from disk as part of
> your executable) -- you don't need extra memory for that. In old days MSVC
> was walking stack frames and looking for data it knew should be there. Not
> sure what it is doing nowadays.

Great if it can be implemented without consuming more resources. But that's
not a given: on some ABI, allocating and keeping memory during unwinding may
be necessary.

Can you elaborate on this portion a bit? What kind of information?

 
And that's aside from the exception object itself, which is most
definitely not stored in a static table.

I thought with GCC exception object is constructed on a heap (or in an emergency buffer) before unwinding machinery is invoked...


If there are ABIs like that and C++ starts requiring them to change, it would
be a huge breakage for existing users. Big enough that I can predict
objections strong enough to the C++ Standard change that would back it out.

So, no, you have to cope with the fact that throwing exceptions may fail, even
for the smallest and/or pre-defined types. And it may be that the systems thus
affected are mainstream.

It would a shame... I hope it isn't the case.

Thiago Macieira

unread,
Aug 17, 2017, 12:58:31 AM8/17/17
to std-dis...@isocpp.org
On Wednesday, 16 August 2017 21:32:15 PDT Michael Kilburn wrote:
> Ahh... I see. 'Full overcommit'. Some claim it to be bad practice:
> https://www.etalabs.net/overcommit.html
>
> I personally don't care, it is just a tool. If you don't like it -- don't
> use it. When setting up an environment for my app that is supposed to
> handle OOMs I'll make sure it is switched off.

*How*?

First, there's no portable way of doing that. So your question about how to
portably handle OOM falls short, since as I said you can't do it.

Second, you can't change the kernel VM settings as a regular user. You need to
do that as a privleged user. So your regular app can't do it.

Third, even if you run your application as root, little stops the admin from
shrinking the swap or turning it completely off (swapoff can fail if it can't
free the pages already swapped out to it, but I doubt it takes into
consideration reserved-but-unused space). That shrinks the total VM after it's
been "committed" to a particular process. And running without swap is poor use
of resources, since it prevents the kernel from evicting little used pages in
favour of things that really need RAM.

If you have enough control over the system to overcome the problems above, you
have enough control over the C++ runtime library implementation too.

> In last Linux environment I worked we never had it on -- because OOM-killer
> always chose the most important app to kill, the one that spent last 14
> hours chewing through data that needs to be available before next trading
> day starts :-)

You can non-portably change that behaviour by adjusting its OOM score. But,
again, non-portable solution.

> I don't know if this behavior is allowed by C++ standard. Probably is. If
> yes -- then you are correct, you can't build portable C or C++ app that can
> handle OOMs, but it is not the reason to deny this possibility to those
> systems where OOM-killer isn't used.

I'm not denying it where it is possible. I'm saying you can't require a
portable solution because it can't be done, or would cost too much.

> Also, this is kinda irrelevant -- my biggest grind with 'crashing throw'
> can be summed like this:
> - exceptions and return codes are essentially the same thing -- it is a way
> to pass error object up the call stack, just implemented differently

Except where they're not. As you've shown, throwing can fail, returning can't.

> - you can always pass this error object using C technique
> - why you can't to it using C++ technique?

Because it's not the same thing. It's a more complex beast and requires more
machinery.

Ville Voutilainen

unread,
Aug 17, 2017, 1:02:18 AM8/17/17
to std-dis...@isocpp.org
On 17 August 2017 at 01:21, Michael Kilburn <crusad...@gmail.com> wrote:
>> Your earlier example runs out of stack space. See how it behaves if
>> dynamic allocation is
>> done instead:
>> https://wandbox.org/permlink/wLk01q0LnzEdFJvS
>
>
> Your example throws pointer to an object -- and therefore succeeds. Here is
> how running of stack space looks like, notice the difference in output from
> my original example:
> https://wandbox.org/permlink/dBBaSsLQ9Rwa9WzI

Well, great, you have just shown yourself that you can't catch stack
allocation failures.
If the initialization of an exception object runs into that problem,
you won't get a bad_alloc.

>> Your problem has nothing particular to do with exceptions.
> Yes, it does.

Except that it does not, as your own example shows.

Michael Kilburn

unread,
Aug 17, 2017, 1:06:51 AM8/17/17
to ISO C++ Standard - Discussion


On Wednesday, August 16, 2017 at 11:58:31 PM UTC-5, Thiago Macieira wrote:
On Wednesday, 16 August 2017 21:32:15 PDT Michael Kilburn wrote:
> Ahh... I see. 'Full overcommit'. Some claim it to be bad practice:
> https://www.etalabs.net/overcommit.html
>
> I personally don't care, it is just a tool. If you don't like it -- don't
> use it. When setting up an environment for my app that is supposed to
> handle OOMs I'll make sure it is switched off.

*How*?

By adding smth like this into supporting documentation:
    This application was designed to handle lack out-of-memory situations on it's own. On Linux switch off over commit, please.

And, depending on actual design this can be made optional or mandatory.

 
First, there's no portable way of doing that. So your question about how to
portably handle OOM falls short, since as I said you can't do it.

Second, you can't change the kernel VM settings as a regular user. You need to
do that as a privleged user. So your regular app can't do it.

Third, even if you run your application as root, little stops the admin from
shrinking the swap or turning it completely off (swapoff can fail if it can't
free the pages already swapped out to it, but I doubt it takes into
consideration reserved-but-unused space). That shrinks the total VM after it's
been "committed" to a particular process. And running without swap is poor use
of resources, since it prevents the kernel from evicting little used pages in
favour of things that really need RAM.

If you have enough control over the system to overcome the problems above, you
have enough control over the C++ runtime library implementation too.

> In last Linux environment I worked we never had it on -- because OOM-killer
> always chose the most important app to kill, the one that spent last 14
> hours chewing through data that needs to be available before next trading
> day starts :-)

You can non-portably change that behaviour by adjusting its OOM score. But,
again, non-portable solution.

I wasn't planning to do it in the code.
 

> I don't know if this behavior is allowed by C++ standard. Probably is. If
> yes -- then you are correct, you can't build portable C or C++ app that can
> handle OOMs, but it is not the reason to deny this possibility to those
> systems where OOM-killer isn't used.

I'm not denying it where it is possible. I'm saying you can't require a
portable solution because it can't be done, or would cost too much.

> Also, this is kinda irrelevant -- my biggest grind with 'crashing throw'
> can be summed like this:
> - exceptions and return codes are essentially the same thing -- it is a way
> to pass error object up the call stack, just implemented differently

Except where they're not. As you've shown, throwing can fail, returning can't.

I'd like throwing not to fail.
 
> - you can always pass this error object using C technique
> - why you can't to it using C++ technique?

Because it's not the same thing. It's a more complex beast and requires more
machinery.

I am looking for better answer. I've been coding complex systems for years -- complexity (where you can't avoid it) is no longer a goof reason to not do smth (for me).
Why it can't be made no-fail?

Michael Kilburn

unread,
Aug 17, 2017, 1:13:07 AM8/17/17
to ISO C++ Standard - Discussion


On Thursday, August 17, 2017 at 12:02:18 AM UTC-5, Ville Voutilainen wrote:
On 17 August 2017 at 01:21, Michael Kilburn <crusad...@gmail.com> wrote:
>> Your earlier example runs out of stack space. See how it behaves if
>> dynamic allocation is
>> done instead:
>> https://wandbox.org/permlink/wLk01q0LnzEdFJvS
>
>
> Your example throws pointer to an object -- and therefore succeeds. Here is
> how running of stack space looks like, notice the difference in output from
> my original example:
> https://wandbox.org/permlink/dBBaSsLQ9Rwa9WzI

Well, great, you have just shown yourself that you can't catch stack
allocation failures.
If the initialization of an exception object runs into that problem,
you won't get a bad_alloc.

Can you show me where 'stack allocation' happens in this disassembled code:
?
 

>> Your problem has nothing particular to do with exceptions.
> Yes, it does.

Except that it does not, as your own example shows.

I can't decide if you are trolling me or not...
 

Thiago Macieira

unread,
Aug 17, 2017, 1:14:24 AM8/17/17
to std-dis...@isocpp.org
On Wednesday, 16 August 2017 21:47:50 PDT Michael Kilburn wrote:
> > The problem is where it can store the information it needs to save while
> > those
> > destructors are running. It can't be below the stack, since the
> > destructors
> > may call other functions that consume stack space. Even if they didn't,
> > Unix
> > signals may be delivered and those also consume stack space.
>
> I don't know these details, if I knew -- I would not be asking these
> questions here. I always assumed that stack unwinding can't fail if
> destructors don't throw (and there is enough stack left).
>
> Which information needs to be saved 'on-the-side' to run destructors with
> GCC?

That's exactly the problem. The standard cannot mandate something that would
be unimplementable, be it for either technical unfeasibility or because the
cost for existing implementations would be too high. What you're asking is not
a problem of the standard, it's a problem of the implementations. And though
there are several developers here who are developers in GCC and Clang, they
may not be paying attention to this thread or be aware of the specific details
of the unwind mechanism.

Without checking the code, I would assume that unwinding can't fail once it's
started. The problem is starting it, since the act of throwing requires using
some resources. At the very least, it needs to store the exception object
you've thrown and any information that can be obtained by the C++ API like
std::uncaught_exceptions(), std::current_exception().

But I wouldn't be surprised if certain unwinders need to allocate a bit of
memory per frame being unwound to calculate something, like in the code that
determines whether a catch's expression matches the object thrown. We simply
can't exclude that possibility in other ABIs, even if the IA-64 C++ portable
ABI's libunwind doesn't need to.

Of course, this isn't including the fact that catching by value an object with
non-noexcept copy constructor could throw too.

> > Great if it can be implemented without consuming more resources. But
> > that's
> > not a given: on some ABI, allocating and keeping memory during unwinding
> > may
> > be necessary.
>
> Can you elaborate on this portion a bit? What kind of information?

See above, I'm speculating. The point is that I cannot rule out that
possibility.

Remember also that we need to deal with ABIs that may have been patched over
and over for the last 25-30 years to deal with C++ and compiler improvements.

> > And that's aside from the exception object itself, which is most
> > definitely not stored in a static table.
>
> I thought with GCC exception object is constructed on a heap (or in an
> emergency buffer) before unwinding machinery is invoked...

And that's exactly the problem. What happens if the object is too large for
the emergency buffer and the heap allocation fails?

Ville Voutilainen

unread,
Aug 17, 2017, 1:24:07 AM8/17/17
to std-dis...@isocpp.org
On 17 August 2017 at 08:13, Michael Kilburn <crusad...@gmail.com> wrote:
>> Well, great, you have just shown yourself that you can't catch stack
>> allocation failures.
>> If the initialization of an exception object runs into that problem,
>> you won't get a bad_alloc.
>
>
> Can you show me where 'stack allocation' happens in this disassembled code:
> https://godbolt.org/g/kcL1ks
> ?

In __cxa_allocate_exception?

>> >> Your problem has nothing particular to do with exceptions.
>> > Yes, it does.
>>
>> Except that it does not, as your own example shows.
>
>
> I can't decide if you are trolling me or not...

You seem to be suggesting that I shouldn't take you seriously. I can
certainly accommodate
such wishes with relatively little effort.

Michael Kilburn

unread,
Aug 17, 2017, 1:28:37 AM8/17/17
to ISO C++ Standard - Discussion


On Thursday, August 17, 2017 at 12:24:07 AM UTC-5, Ville Voutilainen wrote:
On 17 August 2017 at 08:13, Michael Kilburn <crusad...@gmail.com> wrote:
>> Well, great, you have just shown yourself that you can't catch stack
>> allocation failures.
>> If the initialization of an exception object runs into that problem,
>> you won't get a bad_alloc.
>
>
> Can you show me where 'stack allocation' happens in this disassembled code:
> https://godbolt.org/g/kcL1ks
> ?

In __cxa_allocate_exception?



Quote:
Memory will be allocated by the __cxa_allocate_exception runtime library routine. This routine is passed the size of the exception object to be thrown (not including the size of the __cxa_exception header), and returns a pointer to the temporary space for the
 

>> >> Your problem has nothing particular to do with exceptions.
>> > Yes, it does.
>>
>> Except that it does not, as your own example shows.
>
>
> I can't decide if you are trolling me or not...

You seem to be suggesting that I shouldn't take you seriously. I can
certainly accommodate
such wishes with relatively little effort.

I am going to ignore any further non-serious messages from you 

Ville Voutilainen

unread,
Aug 17, 2017, 1:39:55 AM8/17/17
to std-dis...@isocpp.org
On 17 August 2017 at 08:28, Michael Kilburn <crusad...@gmail.com> wrote:
>> You seem to be suggesting that I shouldn't take you seriously. I can
>> certainly accommodate
>> such wishes with relatively little effort.
>
>
> I am going to ignore any further non-serious messages from you


By all means. Once you have convinced the folks on this forum that
there is a problem
that can be solved in a reasonable manner and you have a proposal that
can be considered,
make sure it's backed by a recommendation from a seriously convincing
committee member,
preferably an implementation vendor, lest it is just ignored and not
actually discussed.

Michael Kilburn

unread,
Aug 17, 2017, 1:42:14 AM8/17/17
to ISO C++ Standard - Discussion
I am not sure it is a problem. I don't know for sure that it can be solved. I came here with questions, ended up doing more answering than listening. 

Michael Kilburn

unread,
Aug 17, 2017, 1:50:53 AM8/17/17
to ISO C++ Standard - Discussion


On Wednesday, August 16, 2017 at 8:46:02 PM UTC-5, Patrice Roy wrote:
See https://isocpp.org/std/submit-a-proposal and consider you've begun step 1 :)

If you think you have a valid point, you can come to an actual meeting to express it. It's open and costs nothing (apart from such things as food, lodging and travel costs, which are on you; it's all volunteer work). You probably won't be able to vote in plenary, but you'll be able to participate otherwise, express yourself and take part in straw polls.

I think you'll get (quite reasonable) opposition from people working in memory-constrained environments, and from those who do not use exceptions. You'll probably want to explore what ensuring there's enough memory to throw std::bad_alloc at all times means to these groups (in one case, every byte counts; in the other case, using precious memory for an unused feature). It's a matter of tradeoffs, or so I suppose. You'll want to make a convincing argument.

Another avenue to consider is to contact your compiler vendor(s), as for the moment it's an implementation-specific thing. Maybe that would be sufficient (compiler options or somesuch might do it), and it would be less work for you.

Good luck!

Thank you, Patrice. I should consider it, but chances are it is not going to happen in near future. I clearly don't have full understanding of this and came here with questions, not proposals. Unfortunately, no one who decided to be involved seems to know answers.

Ultimately, if this can't be implemented in most popular compilers without too much blood -- it is bound to fail. And I don't know how it is implemented, nor can I find anyone who knows.

Having one particular vendor to implement it is better than nothing but very far from ideal...

Nicol Bolas

unread,
Aug 17, 2017, 1:51:48 AM8/17/17
to ISO C++ Standard - Discussion
On Thursday, August 17, 2017 at 1:13:07 AM UTC-4, Michael Kilburn wrote:
On Thursday, August 17, 2017 at 12:02:18 AM UTC-5, Ville Voutilainen wrote:
On 17 August 2017 at 01:21, Michael Kilburn <crusad...@gmail.com> wrote:
>> Your earlier example runs out of stack space. See how it behaves if
>> dynamic allocation is
>> done instead:
>> https://wandbox.org/permlink/wLk01q0LnzEdFJvS
>
>
> Your example throws pointer to an object -- and therefore succeeds. Here is
> how running of stack space looks like, notice the difference in output from
> my original example:
> https://wandbox.org/permlink/dBBaSsLQ9Rwa9WzI

Well, great, you have just shown yourself that you can't catch stack
allocation failures.
If the initialization of an exception object runs into that problem,
you won't get a bad_alloc.

Can you show me where 'stack allocation' happens in this disassembled code:
?

Your compiler happens to be eliding the temporary. But if it didn't/couldn't do that, then it would be forced to create a temporary, then copy/move into it.

A temporary that would be on the stack. And thus, not throw `bad_alloc` when it causes a stack overflow.

So why should elision of the large exception cause a recoverable error, when not eliding the exception will cause an unrecoverable failure?

Your general issue is potentially valid, but your specific example is not.

Thiago Macieira

unread,
Aug 17, 2017, 2:05:21 AM8/17/17
to std-dis...@isocpp.org
On Wednesday, 16 August 2017 22:06:50 PDT Michael Kilburn wrote:
> > *How*?
>
> By adding smth like this into supporting documentation:
> This application was designed to handle lack out-of-memory situations
> on it's own. On Linux switch off over commit, please.
>
> And, depending on actual design this can be made optional or mandatory.

So you can also add it to the documentation

"Please use libstdc++safe instead of libstdc++"

> > > Also, this is kinda irrelevant -- my biggest grind with 'crashing throw'
> > > can be summed like this:
> > > - exceptions and return codes are essentially the same thing -- it is a
> >
> > way
> >
> > > to pass error object up the call stack, just implemented differently
> >
> > Except where they're not. As you've shown, throwing can fail, returning
> > can't.
>
> I'd like throwing not to fail.

That's a physical impossibilty, for an arbitrary exception object of unknown
size.

At best, we can adopt your suggestion: an unthrowable exception could be
converted to an exception of a different type (which your catch handlers won't
usually catch) to indicate the situation.

I still think it's a bad idea because you'll most likely unwind too much and
exit frames that did not expect to exit. Including noexcept frames (which will
cause std::terminate).

> I am looking for better answer. I've been coding complex systems for years
> -- complexity (where you can't avoid it) is no longer a goof reason to not
> do smth (for me).
> Why it can't be made no-fail?

That depends on what "no-fail" is. Can it guarantee throwing an arbitrary
exception of any type? No, never. That's easy to see why.

Is it physically possible to write an implementation that can guarantee that
throwing a specific exception type or types will succeed in starting the
unwind, and that unwinding will not by itself fail? Yes, I believe so.

If that runtime implementation can be written, I'd argue that it solves your
problem. You can require your application to use it.

But can we make such requirements to all implementations? Without a survey of
the current implementations, both mainstream and fringe (but not obsolete),
it's difficult to say. It might be possible. But my wild guess here is that such
a survey would find at least one implementation that cannot implement it.

Michael Kilburn

unread,
Aug 17, 2017, 2:06:59 AM8/17/17
to ISO C++ Standard - Discussion


On Thursday, August 17, 2017 at 12:51:48 AM UTC-5, Nicol Bolas wrote:
On Thursday, August 17, 2017 at 1:13:07 AM UTC-4, Michael Kilburn wrote:
On Thursday, August 17, 2017 at 12:02:18 AM UTC-5, Ville Voutilainen wrote:
On 17 August 2017 at 01:21, Michael Kilburn <crusad...@gmail.com> wrote:
>> Your earlier example runs out of stack space. See how it behaves if
>> dynamic allocation is
>> done instead:
>> https://wandbox.org/permlink/wLk01q0LnzEdFJvS
>
>
> Your example throws pointer to an object -- and therefore succeeds. Here is
> how running of stack space looks like, notice the difference in output from
> my original example:
> https://wandbox.org/permlink/dBBaSsLQ9Rwa9WzI

Well, great, you have just shown yourself that you can't catch stack
allocation failures.
If the initialization of an exception object runs into that problem,
you won't get a bad_alloc.

Can you show me where 'stack allocation' happens in this disassembled code:
?

Your compiler happens to be eliding the temporary. But if it didn't/couldn't do that, then it would be forced to create a temporary, then copy/move into it.

In this case you'd get different message, as demonstrated by my previous example. Which I provided in response to "your code overflows the stack" as a proof that stack overflow results in different outcome than what was observed.


 
A temporary that would be on the stack. And thus, not throw `bad_alloc` when it causes a stack overflow.

So why should elision of the large exception cause a recoverable error, when not eliding the exception will cause an unrecoverable failure?

Here is my view on this -- there is no copy elision here. Memory for exception object is allocated in "unspecified way" then expression is used to construct an object there. If implementation (MSVC) decides to use stack for this allocation -- error may not be recoverable (depends if compiler can detect where stack ends or not; and if decided to do this check at all). In any case stack overflow is an acknowledged case when app should die.

If implementation (GCC) uses other means -- it should be recoverable, right? This is basically a variation of the question I asked in original post but reformulated for GCC. I don't know the answer -- I am looking for it right now, in this forum. Because this is the best place I found to discuss it so far.

 
Your general issue is potentially valid, but your specific example is not.

It was a specific example compiled on specific compiler to show that while C++ standard does not mandate specific behavior in this case -- popular implementation chose std::terminate. How this not a valid example?

Michael Kilburn

unread,
Aug 17, 2017, 2:29:32 AM8/17/17
to ISO C++ Standard - Discussion
On Thursday, August 17, 2017 at 1:05:21 AM UTC-5, Thiago Macieira wrote:
On Wednesday, 16 August 2017 22:06:50 PDT Michael Kilburn wrote:
> > *How*?
>
> By adding smth like this into supporting documentation:
>     This application was designed to handle lack out-of-memory situations
> on it's own. On Linux switch off over commit, please.
>
> And, depending on actual design this can be made optional or mandatory.

So you can also add it to the documentation

"Please use libstdc++safe instead of libstdc++"

This might be actually a good idea... I really would like it to be mandated on a language level (if possible, of course).

 
> > > Also, this is kinda irrelevant -- my biggest grind with 'crashing throw'
> > > can be summed like this:
> > > - exceptions and return codes are essentially the same thing -- it is a
> >
> > way
> >
> > > to pass error object up the call stack, just implemented differently
> >
> > Except where they're not. As you've shown, throwing can fail, returning
> > can't.
>
> I'd like throwing not to fail.

That's a physical impossibilty, for an arbitrary exception object of unknown
size.

At best, we can adopt your suggestion: an unthrowable exception could be
converted to an exception of a different type (which your catch handlers won't
usually catch) to indicate the situation.

Yes, this is what I meant. My apologies. If standard says that "throw XXX" can also throw std::bad_alloc (or std::cant_throw) -- my handlers will be catching it.
 

I still think it's a bad idea because you'll most likely unwind too much and
exit frames that did not expect to exit. Including noexcept frames (which will
cause std::terminate).

Only if you let it slip through... But this situation isn't different from any other exception escaping when it shouldn't.
 

> I am looking for better answer. I've been coding complex systems for years
> -- complexity (where you can't avoid it) is no longer a goof reason to not
> do smth (for me).
> Why it can't be made no-fail?

That depends on what "no-fail" is. Can it guarantee throwing an arbitrary
exception of any type? No, never. That's easy to see why.

Is it physically possible to write an implementation that can guarantee that
throwing a specific exception type or types will succeed in starting the
unwind, and that unwinding will not by itself fail? Yes, I believe so.

If that runtime implementation can be written, I'd argue that it solves your
problem. You can require your application to use it.

I think most people here have an impression that I report a problem and propose a solution. It isn't correct. I am asking questions -- is it a problem? Why it can't be done differently?

I came to learn, not to fight. I have no solution. I may have some ideas and I'd like to discuss them with someone who understands subject matter better than me.

Regarding way of implementing it -- I'd aim for smth like this:
- std::bad_alloc is pre-allocated and same object is used in every "throw std::bad_alloc"
   or
- throwing std::bad_alloc is implemented as __cxx_throw(NULL) -- i.e. smth that does not consume any additional memory and is guaranteed to succeed. We literally need only to pass an 'out-of-memory' flag to exception handling machinery.

- if throw fails to allocate --> throw std::bad_alloc
- ... luckily exception specs are deprecated

 
But can we make such requirements to all implementations? Without a survey of
the current implementations, both mainstream and fringe (but not obsolete),
it's difficult to say. It might be possible. But my wild guess here is that such
a survey would find at least one implementation that cannot implement it.

Who should I talk to to get this ball rolling? Maybe there is a better person to discuss this with? Should I try to come up with proposal as Patrice suggested? (it is hard to come up with proposal without knowing precisely how stuff you want to change operates right now...)

Michael Kilburn

unread,
Aug 17, 2017, 2:41:32 AM8/17/17
to ISO C++ Standard - Discussion
Note: we are already discussing some of questions below on different branch.


On Thursday, August 17, 2017 at 12:14:24 AM UTC-5, Thiago Macieira wrote:
On Wednesday, 16 August 2017 21:47:50 PDT Michael Kilburn wrote:
> > The problem is where it can store the information it needs to save while
> > those
> > destructors are running. It can't be below the stack, since the
> > destructors
> > may call other functions that consume stack space. Even if they didn't,
> > Unix
> > signals may be delivered and those also consume stack space.
>
> I don't know these details, if I knew -- I would not be asking these
> questions here. I always assumed that stack unwinding can't fail if
> destructors don't throw (and there is enough stack left).
>
> Which information needs to be saved 'on-the-side' to run destructors with
> GCC?

That's exactly the problem. The standard cannot mandate something that would
be unimplementable, be it for either technical unfeasibility or because the
cost for existing implementations would be too high. What you're asking is not
a problem of the standard, it's a problem of the implementations.

If standard doesn't mandate any behavior -- you can't write code that uses it. You'll end up with chain of ifdefs -- one for each implementation.

 
And though
there are several developers here who are developers in GCC and Clang, they
may not be paying attention to this thread or be aware of the specific details
of the unwind mechanism.

Shame, but I don't blame them -- I think we are about to hit 100 posts here. Who is going to dig through this?

 
Without checking the code, I would assume that unwinding can't fail once it's
started. The problem is starting it, since the act of throwing requires using
some resources. At the very least, it needs to store the exception object
you've thrown and any information that can be obtained by the C++ API like
std::uncaught_exceptions(), std::current_exception().

As far as understand MSVC copies exception from stack to heap in std::current_exception(), GCC doesn't need to. Since this function is noexcept -- I imagine MSVC performs std::bad_alloc substitution there. At least I hope...
 

But I wouldn't be surprised if certain unwinders need to allocate a bit of
memory per frame being unwound to calculate something, like in the code that
determines whether a catch's expression matches the object thrown. We simply
can't exclude that possibility in other ABIs, even if the IA-64 C++ portable
ABI's libunwind doesn't need to.

Of course, this isn't including the fact that catching by value an object with
non-noexcept copy constructor could throw too.

Behavior in this case is defined by standard.

 
> > Great if it can be implemented without consuming more resources. But
> > that's
> > not a given: on some ABI, allocating and keeping memory during unwinding
> > may
> > be necessary.
>
> Can you elaborate on this portion a bit? What kind of information?

See above, I'm speculating. The point is that I cannot rule out that
possibility.

And I am trying to get answers :)
 
Remember also that we need to deal with ABIs that may have been patched over
and over for the last 25-30 years to deal with C++ and compiler improvements.

Why? Just like with other C++ features -- given compiler can just declare that it doesn't support it until version X.X.X.

 
> > And that's aside from the exception object itself, which is most
> > definitely not stored in a static table.
>
> I thought with GCC exception object is constructed on a heap (or in an
> emergency buffer) before unwinding machinery is invoked...

And that's exactly the problem. What happens if the object is too large for
the emergency buffer and the heap allocation fails?

auto-substitution to no-fail "throw std::bad_alloc" or "throw std::cant_throw". Assuming it is possible.

Thiago Macieira

unread,
Aug 17, 2017, 3:32:18 AM8/17/17
to std-dis...@isocpp.org
On Wednesday, 16 August 2017 23:41:32 PDT Michael Kilburn wrote:
> > Remember also that we need to deal with ABIs that may have been patched
> > over
> > and over for the last 25-30 years to deal with C++ and compiler
> > improvements.
>
> Why? Just like with other C++ features -- given compiler can just declare
> that it doesn't support it until version X.X.X.

You're assuming that the compiler can make that change in the first place. That
is not a given, since it could be a major ABI-breaking change. There's more to
this than a theorerical possibility: this change would have large impacts in
support and compatibility of existing code. It may be theoretically possible
but not economically viable.

I don't know if you were around when we did an ABI change the last time (for
GCC 3.3 to 3.4). That made the C++11 std::string change in libstdc++ look like
a minor hiccup in comparison -- I did a system upgrade one day and it was
over.

> > > > And that's aside from the exception object itself, which is most
> > > > definitely not stored in a static table.
> > >
> > > I thought with GCC exception object is constructed on a heap (or in an
> > > emergency buffer) before unwinding machinery is invoked...
> >
> > And that's exactly the problem. What happens if the object is too large
> > for
> > the emergency buffer and the heap allocation fails?
>
> auto-substitution to no-fail "throw std::bad_alloc" or "throw
> std::cant_throw". Assuming it is possible.

And assuming it's a good idea. I'm not satisfied it is.

Take the following example:

void f()
{
throw LargeObject;
}
void g() noexcept
[
try {
f();
} catch (const LargeObject &o) {
return;
}
}

Is the code above safe?

If the throw in f() does throw LargeObject, then g() will catch it and consume
the exception. That function is appropriately noexcept.

But if the throw in f() replaces LargeObject with something else -- ANYTHING
else -- then the catch block in g() won't catch it. Since the g() function is
marked noexcept, the unwinder runtime will call std::terminate().

Now, this is no different than guaranteeing a call to std::terminate() at the
throw point, since you end up there anyway. But the type replacement does
allow unwinding in other code conditions.

Still, we haven't ruled out the unwinder failing in the first place, unrelated
to the type in question. So long as that is a possibility, there will be the
need for an escape hatch that isn't an exception.

Thiago Macieira

unread,
Aug 17, 2017, 3:41:03 AM8/17/17
to std-dis...@isocpp.org
On Wednesday, 16 August 2017 23:29:32 PDT Michael Kilburn wrote:
> > But can we make such requirements to all implementations? Without a survey
> > of
> > the current implementations, both mainstream and fringe (but not
> > obsolete),
> > it's difficult to say. It might be possible. But my wild guess here is
> > that such
> > a survey would find at least one implementation that cannot implement it.
>
> Who should I talk to to get this ball rolling? Maybe there is a better
> person to discuss this with? Should I try to come up with proposal as
> Patrice suggested? (it is hard to come up with proposal without knowing
> precisely how stuff you want to change operates right now...)

My guess is that person that looks back at you when you brush your teeth or
shave in the morning: yourself. (I suppose your dog could also be looking at
you, but the dog won't be of much help. If it's a cat, it might actually make
matters worse if the cat may be plotting the downfall of human civilisation)

I think that the part about changing types if the throw mechanism can't throw
that type can be solved with a simple paper making the proposal. The committee
and compiler writers would see it and discuss the issue.

You should also include in the paper the further problems associated with
heap-free unwinding, beyond the ability to store the exception object itself.
But unless you do the survey I mentioned above, I don't think you can propose
solutions. But you can at least get the ball rolling by getting the vendors to
survey themselves.

I'd also write "stack overflow is out of scope".

Michael Kilburn

unread,
Aug 17, 2017, 3:43:10 AM8/17/17
to ISO C++ Standard - Discussion


On Thursday, August 17, 2017 at 2:32:18 AM UTC-5, Thiago Macieira wrote:
On Wednesday, 16 August 2017 23:41:32 PDT Michael Kilburn wrote:
> > Remember also that we need to deal with ABIs that may have been patched
> > over
> > and over for the last 25-30 years to deal with C++ and compiler
> > improvements.
>
> Why? Just like with other C++ features -- given compiler can just declare
> that it doesn't support it until version X.X.X.

You're assuming that the compiler can make that change in the first place. That
is not a given, since it could be a major ABI-breaking change. There's more to
this than a theorerical possibility: this change would have large impacts in
support and compatibility of existing code. It may be theoretically possible
but not economically viable.

Sigh... Yes, I understand.
 

I don't know if you were around when we did an ABI change the last time (for
GCC 3.3 to 3.4). That made the C++11 std::string change in libstdc++ look like
a minor hiccup in comparison -- I did a system upgrade one day and it was
over.

I was always complaining about COW in libstd++'s implementation of std::string -- turns every single s[i] into potentially throwing :-D

 
> > > > And that's aside from the exception object itself, which is most
> > > > definitely not stored in a static table.
> > >
> > > I thought with GCC exception object is constructed on a heap (or in an
> > > emergency buffer) before unwinding machinery is invoked...
> >
> > And that's exactly the problem. What happens if the object is too large
> > for
> > the emergency buffer and the heap allocation fails?
>
> auto-substitution to no-fail "throw std::bad_alloc" or "throw
> std::cant_throw". Assuming it is possible.

And assuming it's a good idea. I'm not satisfied it is.

Take the following example:

void f()
{
    throw LargeObject;
}
void g() noexcept
[
    try {
        f();
    } catch (const LargeObject &o) {
        return;
    }
}

Is the code above safe?

I hate to nitpick, but if LargeObject is a variable and it's cctor throws -- it is broken in any case. It is also broken if you write it like this:
     throw LargeObject(...);

and it's ctor can throw something (different from LargeObject).

Assuming that change we discussed is implemented throw becomes similar to new -- you expect it to do one thing, but it also can throw std::bad_alloc. 


 
If the throw in f() does throw LargeObject, then g() will catch it and consume
the exception. That function is appropriately noexcept.

But if the throw in f() replaces LargeObject with something else -- ANYTHING
else -- then the catch block in g() won't catch it. Since the g() function is
marked noexcept, the unwinder runtime will call std::terminate().
 
Now, this is no different than guaranteeing a call to std::terminate() at the
throw point, since you end up there anyway. But the type replacement does
allow unwinding in other code conditions.

Still, we haven't ruled out the unwinder failing in the first place, unrelated
to the type in question. So long as that is a possibility, there will be the
need for an escape hatch that isn't an exception.

Well, we already have one -- terminate handler. But it is very limited.

Michael Kilburn

unread,
Aug 17, 2017, 3:54:01 AM8/17/17
to ISO C++ Standard - Discussion


On Thursday, August 17, 2017 at 2:41:03 AM UTC-5, Thiago Macieira wrote:
On Wednesday, 16 August 2017 23:29:32 PDT Michael Kilburn wrote:
> > But can we make such requirements to all implementations? Without a survey
> > of
> > the current implementations, both mainstream and fringe (but not
> > obsolete),
> > it's difficult to say. It might be possible. But my wild guess here is
> > that such
> > a survey would find at least one implementation that cannot implement it.
>
> Who should I talk to to get this ball rolling? Maybe there is a better
> person to discuss this with? Should I try to come up with proposal as
> Patrice suggested? (it is hard to come up with proposal without knowing
> precisely how stuff you want to change operates right now...)

My guess is that person that looks back at you when you brush your teeth or
shave in the morning: yourself. (I suppose your dog could also be looking at
you, but the dog won't be of much help. If it's a cat, it might actually make
matters worse if the cat may be plotting the downfall of human civilisation)

So many assumptions... That I am shaving... in the mornings... and own a mirror... or brush my teeth...
I could be the cat you've mentioned and this discussion is the first step in my elaborate and bullet-proof plot to bring doom to humans.
:-)
 

I think that the part about changing types if the throw mechanism can't throw
that type can be solved with a simple paper making the proposal. The committee
and compiler writers would see it and discuss the issue.

You should also include in the paper the further problems associated with
heap-free unwinding, beyond the ability to store the exception object itself.
But unless you do the survey I mentioned above, I don't think you can propose
solutions. But you can at least get the ball rolling by getting the vendors to
survey themselves.

I'd also write "stack overflow is out of scope".

Thank you. I'll see what I can come up with.
It is loading more messages.
0 new messages