Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

How exceptions are implemented?

201 views
Skip to first unread message

Thiago Adams

unread,
Jul 17, 2018, 9:18:06 AM7/17/18
to
What I am curious about, is how C++ implementations
keep track of destructors that must be called for
stack unwinding.

For instance, if function F throws the destructor
of X is called but the destructor of Y is not.

int main()
{
X x;
F();
Y y;
}

One way I can imagine is to have an state machine
and annotate the state after constructor of X.
and to call the destructors depending on what was
the final state before exception.

Other way that I guess is simple is to have something
like a linked list of destructors and build this list
after each constructor success.




Bo Persson

unread,
Jul 17, 2018, 9:38:10 AM7/17/18
to
A popular way is table-driven exception handling, where a table
describes what has to be done based on where you are in the executable
when the exception is thrown.

Here is one version:

https://itanium-cxx-abi.github.io/cxx-abi/exceptions.pdf



Bo Persson

Paavo Helde

unread,
Jul 17, 2018, 9:49:51 AM7/17/18
to
Such approaches were indeed used in the past (i.e. in the last century).
AFAIK, nowadays the compilers attempt to have zero overhead in the
non-exceptional path. Only when an exception actually appears, the code
will start to figure out in which point in the code it actually is and
which destructors need to be run. The needed information is stored away
in some tables elsewhere in the executable, in order to not interfere
with the normal program execution.

Thiago Adams

unread,
Jul 17, 2018, 10:16:20 AM7/17/18
to
I think where the documents says "landing pad"
is something like I was imagining as states.
Maybe landing state.

But I cannot find where the state is changed.
It must be something dynamic because the code generated
for each function cannot see the internals of the
called functions. (like F)

I think when the stack is unwinding each function is visited
and each function has it's own state in some place (maybe stack)
that is used to understand what destructors must be called.

I guess depending on the code we write we can have more states
and bigger code (tables).
Grouping all destructor maybe is better.

for instance:

int main()
{
X x;
Y y;
F();
}
in this case if X and Y ctor don't throw then
the only state is call ~X ~Y

So using noexcept also could help generate less
tables (states)



Alf P. Steinbach

unread,
Jul 17, 2018, 10:27:12 AM7/17/18
to
On 17.07.2018 15:17, Thiago Adams wrote:
> What I am curious about, is how C++ implementations
> keep track of destructors that must be called for
> stack unwinding.
>
> For instance, if function F throws the destructor
> of X is called but the destructor of Y is not.
>
> int main()
> {
> X x;
> F();
> Y y;
> }

In this case, without a `catch` that catches the exception, you are not
guaranteed that destructors are called. It's up to the implementation.


> One way I can imagine is to have an state machine
> and annotate the state after constructor of X.
> and to call the destructors depending on what was
> the final state before exception.
>
> Other way that I guess is simple is to have something
> like a linked list of destructors and build this list
> after each constructor success.

Yeah. :)


Cheers!,

- Alf

Thiago Adams

unread,
Jul 17, 2018, 10:29:46 AM7/17/18
to
I put the code at compiler explorer site
an I can see that there are two codes, one
calls ~Y ~X and the other calls ~X

I think they represent the "landing pads"
~Y ~X is the success code
and
~X is when F1 throws.

void F(int i)
{
X x;
F1(i);
Y y;
}

F(int):
.LFB14:
push rbp
mov rbp, rsp
push rbx
sub rsp, 40
mov DWORD PTR [rbp-36], edi
lea rax, [rbp-17]
mov rdi, rax
.LEHB0:
call X::X() [complete object constructor]
.LEHE0:
mov eax, DWORD PTR [rbp-36]
mov edi, eax
.LEHB1:
call F1(int)
lea rax, [rbp-18]
mov rdi, rax
call Y::Y() [complete object constructor]
.LEHE1:
lea rax, [rbp-18]
mov rdi, rax
call Y::~Y() [complete object destructor]
lea rax, [rbp-17]
mov rdi, rax
call X::~X() [complete object destructor]
jmp .L12
.L11:
mov rbx, rax
lea rax, [rbp-17]
mov rdi, rax
call X::~X() [complete object destructor]
mov rax, rbx
mov rdi, rax
.LEHB2:
call _Unwind_Resume
.LEHE2:
.L12:
add rsp, 40
pop rbx
pop rbp
ret
.LFE14:
.LLSDA14:
.LLSDACSB14:
.LLSDACSE14:


Thiago Adams

unread,
Jul 17, 2018, 10:32:20 AM7/17/18
to
On Tuesday, July 17, 2018 at 11:27:12 AM UTC-3, Alf P. Steinbach wrote:
> On 17.07.2018 15:17, Thiago Adams wrote:
> > What I am curious about, is how C++ implementations
> > keep track of destructors that must be called for
> > stack unwinding.
> >
> > For instance, if function F throws the destructor
> > of X is called but the destructor of Y is not.
> >
> > int main()
> > {
> > X x;
> > F();
> > Y y;
> > }
>
> In this case, without a `catch` that catches the exception, you are not
> guaranteed that destructors are called. It's up to the implementation.

My test code is:


#include <stdio.h>


struct X
{
X()
{
printf("X()\n");
}
~X()
{
printf("~X()\n");
}
};

struct Y
{
Y()
{
printf("Y()\n");
}
~Y()
{
printf("~Y()\n");
}
};


void F2(int i)
{
if (i > 0)
{
throw 1;
}
}

void F1(int i)
{
F2(i);
}

void F(int i)
{
X x;
F1(i);
Y y;
}

int main()
{
try
{
F(3);
}
catch(...)
{
}
return 0;
}

Alf P. Steinbach

unread,
Jul 17, 2018, 12:21:22 PM7/17/18
to
On 17.07.2018 16:32, Thiago Adams wrote:
> On Tuesday, July 17, 2018 at 11:27:12 AM UTC-3, Alf P. Steinbach wrote:
>> On 17.07.2018 15:17, Thiago Adams wrote:
>>> What I am curious about, is how C++ implementations
>>> keep track of destructors that must be called for
>>> stack unwinding.
>>>
>>> For instance, if function F throws the destructor
>>> of X is called but the destructor of Y is not.
>>>
>>> int main()
>>> {
>>> X x;
>>> F();
>>> Y y;
>>> }
>>
>> In this case, without a `catch` that catches the exception, you are not
>> guaranteed that destructors are called. It's up to the implementation.
>
> [snip]
> > int main()
> {
> try
> {
> F(3);
> }
> catch(...)
> {
> }
> return 0;
> }
>

Yes, that `catch` does make a difference. Note that the `return 0;` is
superfluous. It's the default for `main`, in both C and C++.


Cheers!,

- Alf

Juha Nieminen

unread,
Jul 18, 2018, 3:58:03 AM7/18/18
to
Paavo Helde <myfir...@osa.pri.ee> wrote:
> AFAIK, nowadays the compilers attempt to have zero overhead in the
> non-exceptional path.

Indeed. It is my understanding that the C++ standardization committee only
standardized exception handling into C++98 once they were convinced that
it's possible to have a zero-overhead implementation of them (in the case
that exceptions are not thrown). In other words, if no exceptions are
thrown, there is no speed difference between supporting and not supporting
throwing exceptions. They upheld the "you don't pay for what you don't use"
principle quite strictly here. (In this case it means that code that isn't
using exceptions shouldn't suffer a speed penalty just because it has to
be prepared for an exception to happen somewhere along the line.)

Juha Nieminen

unread,
Jul 18, 2018, 4:00:27 AM7/18/18
to
Alf P. Steinbach <alf.p.stein...@gmail.com> wrote:
> Yes, that `catch` does make a difference. Note that the `return 0;` is
> superfluous. It's the default for `main`, in both C and C++.

It's unclear to me if "return 0;" or "return EXIT_SUCCESS;" is the default
in C++ when no explicit return is specified in main().

(In the vast majority of systems EXIT_SUCCESS is 0, but I think theoretically
it could be something else.)

morealfw...@cylonhq.com

unread,
Jul 18, 2018, 5:03:58 AM7/18/18
to
On Tue, 17 Jul 2018 18:21:12 +0200
"Alf P. Steinbach" <alf.p.stein...@gmail.com> wrote:
Int is also the default return type for main() so lets bin that too, oh and
untyped function parameters default to int so lets save a few bytes and don't
bother putting it in. Now try compiling your dream code and see what the
compiler has to say about it.

You do love to put brevity above clarity, were you a Perl coder in a past
life?

Ralf Fassel

unread,
Jul 18, 2018, 5:23:53 AM7/18/18
to
* "Alf P. Steinbach" <alf.p.stein...@gmail.com>
| On 17.07.2018 15:17, Thiago Adams wrote:
| > int main()
| > {
| > X x;
| > F();
| > Y y;
| > }
>
| In this case, without a `catch` that catches the exception, you are
| not guaranteed that destructors are called. It's up to the
| implementation.

You mean, main() is special with regards to exceptions and DTORs? Isn't
the whole point of exception safety actually to guarantee that DTORs are
called when exceptions occur? Consider std::lock_guard and related...

IMHO in the above the DTOR of X should always get called regardless of
F() throwing or not.

R'

Bo Persson

unread,
Jul 18, 2018, 6:27:11 AM7/18/18
to
No, main is not special (in this regard). The difference is having a
try-catch, or not.

Implementations are allowed to first go looking for the catch clause,
and only then run the destructors for the stack objects. Or it could
unwind one stack frame at a time while searching for the catch-clause.

Makes a difference if the is NO try-catch in the current line of
execution, so that the thrown exception leaves main. Could mean that the
program is just terminated without executing any destructors.


Bo Persson

Ben Bacarisse

unread,
Jul 18, 2018, 7:35:51 AM7/18/18
to
Juha Nieminen <nos...@thanks.invalid> writes:

> Alf P. Steinbach <alf.p.stein...@gmail.com> wrote:
>> Yes, that `catch` does make a difference. Note that the `return 0;` is
>> superfluous. It's the default for `main`, in both C and C++.
>
> It's unclear to me if "return 0;" or "return EXIT_SUCCESS;" is the default
> in C++ when no explicit return is specified in main().

When there is no explicit return, main behaves as if return 0; were
executed. This is the same in C and C++.

> (In the vast majority of systems EXIT_SUCCESS is 0, but I think theoretically
> it could be something else.)

It's true that EXIT_SUCCESS need not be equal to 0, but a return from
main with either value (or, indeed, a call to std::exit with either
value as the argument) will be seen as a successful termination.

Obviously neither standard can really say much about what that means,
but both C and C++ it make it clear that they are equivalent as far as
signalling success or failure is concerned.

--
Ben.

james...@alumni.caltech.edu

unread,
Jul 18, 2018, 7:57:15 AM7/18/18
to
On Wednesday, July 18, 2018 at 4:00:27 AM UTC-4, Juha Nieminen wrote:
...
> It's unclear to me if "return 0;" or "return EXIT_SUCCESS;" is the default
> in C++ when no explicit return is specified in main().

"If control reaches the end of main without encountering a return statement,
the effect is that of executing
return 0;" (3.6.1p5)

Paavo Helde

unread,
Jul 18, 2018, 8:04:42 AM7/18/18
to
In 18.5/8: "If status is zero or EXIT_SUCCESS, an implementation-defined
form of the status successful termination is returned."

So 0 and EXIT_SUCCESS appear to be at least interchangeable, even if not
exactly the same.



Thiago Adams

unread,
Jul 18, 2018, 9:05:07 AM7/18/18
to
On Wednesday, July 18, 2018 at 4:58:03 AM UTC-3, Juha Nieminen wrote:
It must have some overhead in size because it has to build
the "landing pads" and tables state->landing pads.
Also it has to change some state (let's say change one pointer,
I don't known) while the code is completing ctors.

Maybe zero overhead is compared with manual solution
of error codes.

I don't understand how heavy is the computation on
stack unwinding, but the document [1] says it uses
compression. The compression means that the states
table can be large.

https://itanium-cxx-abi.github.io/cxx-abi/exceptions.pdf


Something interesting is to compare the size of the current
exception mechanisms against this:

Zero-overhead deterministic exceptions: Throwing values
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0709r0.pdf


The "zero-overhead" is also something unrealistic in my option.
Error propagation has a cost.

The memory that holds the throw object also
is especial - not on the stack - and some
implementations may have to allocate the memory
dynamically (not sure) for this object.

Maybe this is the reason why some people says
that the computation after throw is not deterministic.




Alf P. Steinbach

unread,
Jul 18, 2018, 10:21:04 AM7/18/18
to
On 18.07.2018 11:03, morealfw...@cylonHQ.com wrote:
> On Tue, 17 Jul 2018 18:21:12 +0200
> "Alf P. Steinbach" <alf.p.stein...@gmail.com> wrote:
>> [snip]
>> Yes, that `catch` does make a difference. Note that the `return 0;` is
>> superfluous. It's the default for `main`, in both C and C++.
>
> Int is also the default return type for main()

You're posting to a C++ group only.

Implicit int was removed in the first C++ standard, in 1998, with the
following rationale:

C++98 Annex §C1.5 clause 7.1.5, “In C++, implicit int creates several
opportunities for ambiguity between expressions involving function-like
casts and declarations. Explicit declaration is increasingly considered
to be proper style. Liaison with WG14 (C) indicated support for (at
least) deprecating implicit int in the next revision of C.”


> so lets bin that too, oh and
> untyped function parameters default to int

Nope. See above.


> so lets save a few bytes and don't
> bother putting it in. Now try compiling your dream code and see what the
> compiler has to say about it.
>
> You do love to put brevity above clarity, were you a Perl coder in a past
> life?

This personal attack following a demonstration of incompetence, sounds
like a “Boltar” that I've killfiled six identities of.

Plink.

alfswibb...@theshed.com

unread,
Jul 18, 2018, 11:19:09 AM7/18/18
to
On Wed, 18 Jul 2018 16:20:51 +0200
"Alf P. Steinbach" <alf.p.stein...@gmail.com> wrote:
>On 18.07.2018 11:03, morealfw...@cylonHQ.com wrote:
>> On Tue, 17 Jul 2018 18:21:12 +0200
>> "Alf P. Steinbach" <alf.p.stein...@gmail.com> wrote:
>>> [snip]
>>> Yes, that `catch` does make a difference. Note that the `return 0;` is
>>> superfluous. It's the default for `main`, in both C and C++.
>>
>> Int is also the default return type for main()
>
>You're posting to a C++ group only.

Except you mentioned C.

>Implicit int was removed in the first C++ standard, in 1998, with the
>following rationale:

See above.

>C++98 Annex §C1.5 clause 7.1.5, “In C++, implicit int creates several
>opportunities for ambiguity between expressions involving function-like
>casts and declarations. Explicit declaration is increasingly considered
>to be proper style. Liaison with WG14 (C) indicated support for (at
>least) deprecating implicit int in the next revision of C.”

Fascinating.

>> so lets bin that too, oh and
>> untyped function parameters default to int
>
>Nope. See above.

For C yes. See above.

>This personal attack following a demonstration of incompetence, sounds
>like a “Boltar” that I've killfiled six identities of.
>
>Plink.

Your killfile must be full of junk by now. You obviously still haven't figured
out that I just generate these id's at random with no limit. Perhaps educate
yourself as to how NNTP works.

Bo Persson

unread,
Jul 18, 2018, 11:24:02 AM7/18/18
to
On 2018-07-18 15:04, Thiago Adams wrote:
> On Wednesday, July 18, 2018 at 4:58:03 AM UTC-3, Juha Nieminen wrote:
>> Paavo Helde wrote:
>>> AFAIK, nowadays the compilers attempt to have zero overhead in the
>>> non-exceptional path.
>>
>> Indeed. It is my understanding that the C++ standardization committee only
>> standardized exception handling into C++98 once they were convinced that
>> it's possible to have a zero-overhead implementation of them (in the case
>> that exceptions are not thrown). In other words, if no exceptions are
>> thrown, there is no speed difference between supporting and not supporting
>> throwing exceptions. They upheld the "you don't pay for what you don't use"
>> principle quite strictly here. (In this case it means that code that isn't
>> using exceptions shouldn't suffer a speed penalty just because it has to
>> be prepared for an exception to happen somewhere along the line.)
>
> It must have some overhead in size because it has to build
> the "landing pads" and tables state->landing pads.

This can be a disk size overhead only. Using virtual memory the tables
and code can be swapped in on demand.

And most of the possible exceptions will not be thrown during a normal
execution. So doesn't really have to be loaded.

> Also it has to change some state (let's say change one pointer,
> I don't known) while the code is completing ctors.

The constructors would have to execute anyway, so how is this an overhead?

>
> Maybe zero overhead is compared with manual solution
> of error codes.

Tons of "if (result == error) goto end;" IS overhead. :-)

And is problematic in C++ if the goto jumps over the construction of
some objects.

>
> I don't understand how heavy is the computation on
> stack unwinding, but the document [1] says it uses
> compression. The compression means that the states
> table can be large.

Pretty heavy, but doesn't happen that often.



Bo Persson

Bo Persson

unread,
Jul 18, 2018, 11:27:50 AM7/18/18
to
Oh, I was thinking destructors here. Never mind!

Alf P. Steinbach

unread,
Jul 18, 2018, 11:41:59 AM7/18/18
to
On 18.07.2018 17:18, "Boltar" wrote:
>>
>> Plink.
>
> Your killfile must be full

Eight identity of "Boltar" killfiled.


Paavo Helde

unread,
Jul 18, 2018, 11:58:54 AM7/18/18
to
On 18.07.2018 16:04, Thiago Adams wrote:
>
> It must have some overhead in size because it has to build
> the "landing pads" and tables state->landing pads.

Yes, it trades some disk space for speed, but the size of executables
does not interest most people nowadays (except some nutwits in this group).

> Also it has to change some state (let's say change one pointer,
> I don't known) while the code is completing ctors.

In principle the state of the IP (instruction pointer) could be enough,
don't know how it is actually done.

>
> Maybe zero overhead is compared with manual solution
> of error codes.
>
> I don't understand how heavy is the computation on
> stack unwinding, but the document [1] says it uses
> compression. The compression means that the states
> table can be large.

I believe they deal with relative jump offsets which are typically small
numbers, so they are compressing away the high-order zero bytes. Can
probably give something like 8x compression. Decompressing is a bit
tedious but as this happens on the exceptional code path only it does
not matter.

woodb...@gmail.com

unread,
Jul 18, 2018, 1:03:49 PM7/18/18
to
On Wednesday, July 18, 2018 at 10:58:54 AM UTC-5, Paavo Helde wrote:
> On 18.07.2018 16:04, Thiago Adams wrote:
> >
> > It must have some overhead in size because it has to build
> > the "landing pads" and tables state->landing pads.
>
> Yes, it trades some disk space for speed, but the size of executables
> does not interest most people nowadays (except some nutwits in this group).

Caring about the size of your executables pays off in
terms of runtimes and build times. This talk by Mark
Zeren at Cppnow is a little vindication for the nutwits.

https://duckduckgo.com/?q=cppnow+matters&t=h_&ia=videos&iax=videos&iai=vGV5u1nxqd8


Brian
Ebenezer Enterprises
http://webEbenezer.net


Scott Lurndal

unread,
Jul 18, 2018, 1:07:04 PM7/18/18
to
woodb...@gmail.com writes:
>On Wednesday, July 18, 2018 at 10:58:54 AM UTC-5, Paavo Helde wrote:
>> On 18.07.2018 16:04, Thiago Adams wrote:
>> >
>> > It must have some overhead in size because it has to build
>> > the "landing pads" and tables state->landing pads.
>>
>> Yes, it trades some disk space for speed, but the size of executables
>> does not interest most people nowadays (except some nutwits in this group).
>
>Caring about the size of your executables pays off in
>terms of runtimes and build times.

Nonsense.

> This talk by Mark
>Zeren at Cppnow is a little vindication for the nutwits.

Who is Mark Zeren, and why should I care?

>
>https://duckduckgo.com/?q=cppnow+matters&t=h_&ia=videos&iax=videos&iai=vGV5u1nxqd8

Sorry, can't do videos and don't do duckduckgo. Post the actual link
to words instead of the search terms.

james...@alumni.caltech.edu

unread,
Jul 18, 2018, 1:14:04 PM7/18/18
to
On Wednesday, July 18, 2018 at 11:19:09 AM UTC-4, alfswibb...@theshed.com wrote:
> On Wed, 18 Jul 2018 16:20:51 +0200
> "Alf P. Steinbach" <alf.p.stein...@gmail.com> wrote:
> >On 18.07.2018 11:03, morealfw...@cylonHQ.com wrote:
> >> On Tue, 17 Jul 2018 18:21:12 +0200
> >> "Alf P. Steinbach" <alf.p.stein...@gmail.com> wrote:
> >>> [snip]
> >>> Yes, that `catch` does make a difference. Note that the `return 0;` is
> >>> superfluous. It's the default for `main`, in both C and C++.
> >>
> >> Int is also the default return type for main()
> >
> >You're posting to a C++ group only.
>
> Except you mentioned C.
>
> >Implicit int was removed in the first C++ standard, in 1998, with the
> >following rationale:
>
> See above.

It was removed from the second version of the C standard, in 1999, so that doesn't make much difference.

woodb...@gmail.com

unread,
Jul 18, 2018, 1:26:43 PM7/18/18
to
On Wednesday, July 18, 2018 at 12:07:04 PM UTC-5, Scott Lurndal wrote:
> woodb...@gmail.com writes:
> >On Wednesday, July 18, 2018 at 10:58:54 AM UTC-5, Paavo Helde wrote:
> >> On 18.07.2018 16:04, Thiago Adams wrote:
> >> >
> >> > It must have some overhead in size because it has to build
> >> > the "landing pads" and tables state->landing pads.
> >>
> >> Yes, it trades some disk space for speed, but the size of executables
> >> does not interest most people nowadays (except some nutwits in this group).
> >
> >Caring about the size of your executables pays off in
> >terms of runtimes and build times.
>
> Nonsense.
>
> > This talk by Mark
> >Zeren at Cppnow is a little vindication for the nutwits.
>
> Who is Mark Zeren, and why should I care?
>

He's a developer at VMware:
https://www.linkedin.com/in/markzeren/

His talk at 2018 C++Now was titled "-Os matters".

> >
> >https://duckduckgo.com/?q=cppnow+matters&t=h_&ia=videos&iax=videos&iai=vGV5u1nxqd8
>
> Sorry, can't do videos and don't do duckduckgo. Post the actual link
> to words instead of the search terms.

Here's the overview of the talk:

At VMware we include binary size deltas in code reviews
for large, C++, user-space, applications. You might be
thinking "that's the most pointy haired thing I've every
heard of!". Come to this talk and learn how this simple
metric provides surprisingly strong counter pressure to complexity.

bol...@cylonhq.com

unread,
Jul 19, 2018, 4:41:18 AM7/19/18
to
Still valid in all C compilers.

james...@alumni.caltech.edu

unread,
Jul 19, 2018, 7:32:51 AM7/19/18
to
You've checked every single C compiler ever written? Impressive - I
wouldn't know how to even begin such a review.
There's no point in discussing what non-conforming implementations
of C or C++ do - there's no limits on the possibilities. I'm only
interested in discussing the behavior of compilers when invoked in
a mode that fully conforms to some version of the relevant standard,
and unless otherwise specified, that's the current version. Since
1999, neither the current C standard nor the current C++ standard
has supported implicit int.

bol...@cylonhq.com

unread,
Jul 19, 2018, 9:17:42 AM7/19/18
to
On Thu, 19 Jul 2018 04:32:41 -0700 (PDT)
james...@alumni.caltech.edu wrote:
>On Thursday, July 19, 2018 at 4:41:18 AM UTC-4, bol...@cylonhq.com wrote:
>> >It was removed from the second version of the C standard, in 1999, so that
>> >doesn't make much difference.
>>
>> Still valid in all C compilers.
>
>You've checked every single C compiler ever written? Impressive - I
>wouldn't know how to even begin such a review.

Both gcc and clang require a command line switch to strictly conform to the C99
standard and MSVC++ doesn't even support C99 properly anyway. The de facto
C standard is still C89 and probably always will be.

james...@alumni.caltech.edu

unread,
Jul 19, 2018, 11:51:00 AM7/19/18
to
So "all C compilers" is equivalent to "gcc, clang, and MSVC++"? Wow - what a simple world you live in! Mine's a bit bigger and more complicated than yours. How many different countries are there in your world? How many different languages? Religions? How small is it, really?

David Brown

unread,
Jul 19, 2018, 1:28:37 PM7/19/18
to
On 18/07/18 19:26, woodb...@gmail.com wrote:
> On Wednesday, July 18, 2018 at 12:07:04 PM UTC-5, Scott Lurndal wrote:
>> woodb...@gmail.com writes:
>>> On Wednesday, July 18, 2018 at 10:58:54 AM UTC-5, Paavo Helde wrote:
>>>> On 18.07.2018 16:04, Thiago Adams wrote:
>>>>>
>>>>> It must have some overhead in size because it has to build
>>>>> the "landing pads" and tables state->landing pads.
>>>>
>>>> Yes, it trades some disk space for speed, but the size of executables
>>>> does not interest most people nowadays (except some nutwits in this group).
>>>

It interests embedded programmers too. If you have a fixed size of code
flash on a device, and your program is nearing that limit, size begins
to matter a great deal.

Code size can also be relevant in terms of cache performance - less code
means higher hit ratios. Of course, this does not matter for things
like exception tables - only for the commonly executed code.

>
> Here's the overview of the talk:
>
> At VMware we include binary size deltas in code reviews
> for large, C++, user-space, applications. You might be
> thinking "that's the most pointy haired thing I've every
> heard of!". Come to this talk and learn how this simple
> metric provides surprisingly strong counter pressure to complexity.
>

Binary size deltas can be an interesting measure for code reviews - they
say something about how much of the program has changed. That doesn't
mean that keeping binary sizes small is a useful goal in itself -
keeping /complexity/ low is the aim.

Thiago Adams

unread,
Jul 19, 2018, 1:54:39 PM7/19/18
to
On Wednesday, July 18, 2018 at 10:05:07 AM UTC-3, Thiago Adams wrote:
...
> It must have some overhead in size because it has to build
> the "landing pads" and tables state->landing pads.

Something interesting to compare as well,
is the code generated when we have return points
in the middle of the code.

int F(int i)
{
if (i > 0)
{
X x;
if (i < 2) return 0;
Y y;
if (i < 3) return 0;
Z z;
if (i < 4) return 0;
}
return 0;
}

Differently from exceptions, everything is static.
I believe the size can also be affected having more
return points in the middle of the code and the compiler
will try to reuse some states.

Instead of "landing pad" in this case we have
"exit points" that would call some destructors and
not the others.

I would like to understand how compilers works to generate
this code (in case someone has links about this). Maybe is
something like an DFA with minimization.



Paavo Helde

unread,
Jul 19, 2018, 3:43:25 PM7/19/18
to
On 19.07.2018 20:28, David Brown wrote:
> On 18/07/18 19:26, woodb...@gmail.com wrote:
>> On Wednesday, July 18, 2018 at 12:07:04 PM UTC-5, Scott Lurndal wrote:
>>> woodb...@gmail.com writes:
>>>> On Wednesday, July 18, 2018 at 10:58:54 AM UTC-5, Paavo Helde wrote:
>>>>> On 18.07.2018 16:04, Thiago Adams wrote:
>>>>>>
>>>>>> It must have some overhead in size because it has to build
>>>>>> the "landing pads" and tables state->landing pads.
>>>>>
>>>>> Yes, it trades some disk space for speed, but the size of executables
>>>>> does not interest most people nowadays (except some nutwits in this
>>>>> group).
>>>>
>
> It interests embedded programmers too. If you have a fixed size of code
> flash on a device, and your program is nearing that limit, size begins
> to matter a great deal.

Well, then you compile with -Os or equivalent, and hope the compiler
does the right thing and optimizes for the size. AFAIK there are even
special compilers for embedded devices which can create smaller binaries.

In case of exception handling this means you trade some speed for the
size. TANSTAAFL.

When you do compile without -Os then it means you do not care about the
size. Maybe I should have been more clear I was talking about that regime.

[snipping the rest because my killfile has eaten the parent post]

David Brown

unread,
Jul 19, 2018, 6:27:46 PM7/19/18
to
On 19/07/18 21:43, Paavo Helde wrote:
> On 19.07.2018 20:28, David Brown wrote:
>> On 18/07/18 19:26, woodb...@gmail.com wrote:
>>> On Wednesday, July 18, 2018 at 12:07:04 PM UTC-5, Scott Lurndal wrote:
>>>> woodb...@gmail.com writes:
>>>>> On Wednesday, July 18, 2018 at 10:58:54 AM UTC-5, Paavo Helde wrote:
>>>>>> On 18.07.2018 16:04, Thiago Adams wrote:
>>>>>>>
>>>>>>> It must have some overhead in size because it has to build
>>>>>>> the "landing pads" and tables state->landing pads.
>>>>>>
>>>>>> Yes, it trades some disk space for speed, but the size of executables
>>>>>> does not interest most people nowadays (except some nutwits in this
>>>>>> group).
>>>>>
>>
>> It interests embedded programmers too.  If you have a fixed size of code
>> flash on a device, and your program is nearing that limit, size begins
>> to matter a great deal.
>
> Well, then you compile with -Os or equivalent, and hope the compiler
> does the right thing and optimizes for the size.

Sure, you use the right flags (and the right compiler). And you also
learn how to write code for such systems.

My point is that the size of the binaries can matter here.

> AFAIK there are even
> special compilers for embedded devices which can create smaller binaries.

Of course there are, especially for the very small devices (which are
rarely programmed in C++ anyway).

>
> In case of exception handling this means you trade some speed for the
> size. TANSTAAFL.

That is always the case with exception handling. Table-based exception
implementations are designed to minimise the impact for normal runs, at
the cost of speed or size for dealing with exceptions. Usually that's a
good tradeoff. (In embedded systems, you often disable exceptions
entirely.)

>
> When you do compile without -Os then it means you do not care about the
> size.

No, it certainly does not. It merely means that you are making a choice
other than having the compiler use size as the dominant factor in the
compilation. -O2 compilation can sometimes be smaller, and is often a
much more appropriate balance between size and speed (I'm assuming gcc
here). In particular, regardless of the flags you use you may make
design decisions aimed at smaller code space or at greater speed (or
flexibility, or other criteria).

bol...@cylonhq.com

unread,
Jul 20, 2018, 4:54:11 AM7/20/18
to
On Thu, 19 Jul 2018 08:50:50 -0700 (PDT)
james...@alumni.caltech.edu wrote:
>On Thursday, July 19, 2018 at 9:17:42 AM UTC-4, bol...@cylonhq.com wrote:
>> On Thu, 19 Jul 2018 04:32:41 -0700 (PDT)
>> james...@alumni.caltech.edu wrote:
>> >On Thursday, July 19, 2018 at 4:41:18 AM UTC-4, bol...@cylonhq.com wrote=
>:
>> >> >It was removed from the second version of the C standard, in 1999, so=
> that
>> >> >doesn't make much difference.
>> >>=20
>> >> Still valid in all C compilers.
>> >
>> >You've checked every single C compiler ever written? Impressive - I
>> >wouldn't know how to even begin such a review.
>>=20
>> Both gcc and clang require a command line switch to strictly conform to t=
>he C99=20
>> standard and MSVC++ doesn't even support C99 properly anyway. The de fact=
>o
>> C standard is still C89 and probably always will be.
>
>So "all C compilers" is equivalent to "gcc, clang, and MSVC++"? Wow - what =
>a simple world you live in! Mine's a bit bigger and more complicated than y=
>ours. How many different countries are there in your world? How many differ=
>ent languages? Religions? How small is it, really?

*guffaw*, you should do stand up mate!

gcc, clang and vc++ probably cover 90% or more of the C/C++ compilers used in
the world. Feel free to mention any others that matter that default to C99. And
if you're using a specialised C compiler to write to a microcontroller or
other proprietary devices there's a good chance that

A) It doesn't support C99 anyway
B) You don't have a main() function at all
C) There will be language extensions and modifications that stretch the
definition of "C"

David Brown

unread,
Jul 20, 2018, 2:11:27 PM7/20/18
to
Your numbering is confused. Probably 90% or more of C or C++
programmers use one of these three compilers. But there are a great
many compilers in daily use - a hundred or more, I'd guess, and that is
not counting variations like the dozens of different targets for gcc or
the very significantly different versions of different compilers (there
are people using gcc 2.7 today). Most of these are quite specialised,
or long out of date and only used because people working with serious
projects often insist on keeping the toolchains unchanged. But these
are still C compilers in use for real work. (And then, of course, there
are the hobby compilers or experimental compilers with a few users each
- $DEITY knows how many of these there are around.)

> Feel free to mention any others that matter that default to C99.

Why would "defaults to C99" have the slightest significance? Neither
gcc, clang nor MSVC have ever defaulted to C99. You might, I suppose,
ask for /support/ for at least most of C99 as an indication of being a
modern tool, which would certainly reduce the number of C compilers in
use somewhat.

> And
> if you're using a specialised C compiler to write to a microcontroller or
> other proprietary devices there's a good chance that
>
> A) It doesn't support C99 anyway

Most do support C99, at least the parts that are relevant to the target.

> B) You don't have a main() function at all

I have only twice seen an embedded C program without a main() function -
and in both cases, that was because I choose to write my own pre-C
startup code and didn't bother naming a function "main".

> C) There will be language extensions and modifications that stretch the
> definition of "C"
>

Most have language extensions of some sort, but that does not stop them
being C compilers. Certainly /some/ have enough extensions that are
important to coding, or enough non-conformities, or require such
particular coding styles to get efficient results, that they are
arguably a dialect of C or a C-like language. But most embedded
compilers are used mainly with standard C.

bol...@cylonhq.com

unread,
Jul 22, 2018, 4:43:11 AM7/22/18
to
On Fri, 20 Jul 2018 20:11:12 +0200
David Brown <david...@hesbynett.no> wrote:
>On 20/07/18 10:54, bol...@cylonHQ.com wrote:
>> gcc, clang and vc++ probably cover 90% or more of the C/C++ compilers used in
>
>> the world.
>
>Your numbering is confused. Probably 90% or more of C or C++
>programmers use one of these three compilers. But there are a great

Agreed, that probably is a better way of putting it, my mistake.

>> Feel free to mention any others that matter that default to C99.
>
>Why would "defaults to C99" have the slightest significance? Neither

Because I mentioned the fact that even modern compilers require a switch
to operate in C99 mode and even now the de facto C standard is C89.

>> B) You don't have a main() function at all
>
>I have only twice seen an embedded C program without a main() function -

You've obviously never programmed an arduino then :)


David Brown

unread,
Jul 22, 2018, 7:26:48 AM7/22/18
to
I work with real embedded systems, not toys. I usually only see
Arduinos in terms of potential customers who have got a vague prototype
or proof of concept using an Arduino, and want to develop a product.

Arduinos are great for getting people interested in embedded
programming, and have spawned a wide selection of off-the-shelf cards
for prototyping, but they have a counter-productive design for the
development system. It combines the worst of C with the worst of C++,
hides all the useful stuff in the toolchain, skimps over important
details so that people who learned with Arduino can't program on other
systems, and gives a false impression that embedded development is
something quick and easy. If you understand the limitations of the
system, it can be a useful idea - if not, it is worse than useless.

And Arduino programs have a "main()". It is just hidden from the user.


bol...@cylonhq.com

unread,
Jul 23, 2018, 6:03:42 AM7/23/18
to
On Sun, 22 Jul 2018 13:26:37 +0200
David Brown <david...@hesbynett.no> wrote:
>On 22/07/18 10:43, bol...@cylonHQ.com wrote:
>> You've obviously never programmed an arduino then :)
>>
>
>I work with real embedded systems, not toys. I usually only see

Depends how you define a toy. It can be used for some lightweight applications.

>development system. It combines the worst of C with the worst of C++,
>hides all the useful stuff in the toolchain, skimps over important
>details so that people who learned with Arduino can't program on other

You still have to understand digital vs analogue I/O and timing otherwise
you're not going to get anything done.

>systems, and gives a false impression that embedded development is
>something quick and easy. If you understand the limitations of the
>system, it can be a useful idea - if not, it is worse than useless.

Same with anything really.

Juha Nieminen

unread,
Jul 26, 2018, 2:13:44 AM7/26/18
to
Thiago Adams <thiago...@gmail.com> wrote:
> On Wednesday, July 18, 2018 at 4:58:03 AM UTC-3, Juha Nieminen wrote:
>> Paavo Helde wrote:
>> > AFAIK, nowadays the compilers attempt to have zero overhead in the
>> > non-exceptional path.
>>
>> Indeed. It is my understanding that the C++ standardization committee only
>> standardized exception handling into C++98 once they were convinced that
>> it's possible to have a zero-overhead implementation of them (in the case
>> that exceptions are not thrown). In other words, if no exceptions are
>> thrown, there is no speed difference between supporting and not supporting
>> throwing exceptions. They upheld the "you don't pay for what you don't use"
>> principle quite strictly here. (In this case it means that code that isn't
>> using exceptions shouldn't suffer a speed penalty just because it has to
>> be prepared for an exception to happen somewhere along the line.)
>
> It must have some overhead in size because it has to build
> the "landing pads" and tables state->landing pads.
> Also it has to change some state (let's say change one pointer,
> I don't known) while the code is completing ctors.

I did write "shouldn't suffer a speed penalty".

> The "zero-overhead" is also something unrealistic in my option.
> Error propagation has a cost.

I said "when exceptions are not thrown".

Of course exception-throwing is a very heavy operation. The point is that
support for it doesn't slow down the code when exceptions are *not* thrown.

Support for throwing exceptions could easily slow down code even when
they aren't thrown, if a naive implementation of exceptions is implemented
into the compiler.

Tim Rentsch

unread,
Jul 31, 2018, 4:06:04 AM7/31/18
to
That used to be true but isn't any longer. In particular, gcc
in its default mode used to be based on C89/C90, but now is
based on C11.

bol...@cylonhq.com

unread,
Jul 31, 2018, 4:24:16 AM7/31/18
to
Fair enough. I didn't even realise there was a C 2011 and looking at it the
updates seem fairly specialised and nothing that isn't already in C++ anyway
so I don't think I'll be looking into it much further. Useful to know though.

Chris M. Thomasson

unread,
Jul 31, 2018, 4:40:54 AM7/31/18
to
On 7/31/2018 1:24 AM, bol...@cylonHQ.com wrote:
> On Tue, 31 Jul 2018 01:05:54 -0700
> Tim Rentsch <t...@alumni.caltech.edu> wrote:
>> bol...@cylonHQ.com writes:
>>
>>> On Thu, 19 Jul 2018 04:32:41 -0700 (PDT)
>>> james...@alumni.caltech.edu wrote:
>>>
>>>> On Thursday, July 19, 2018 at 4:41:18 AM UTC-4, bol...@cylonhq.com wrote:
>>>>
>>>>>> It was removed from the second version of the C standard, in
>>>>>> 1999, so that doesn't make much difference.
>>>>>
>>>>> Still valid in all C compilers.
>>>>
>>>> You've checked every single C compiler ever written? Impressive -
>>>> I wouldn't know how to even begin such a review.
>>>
>>> Both gcc and clang require a command line switch to strictly conform
>>> to the C99 standard and MSVC++ doesn't even support C99 properly
>>> anyway. The de facto C standard is still C89 and probably always
>>> will be.
>>
>> That used to be true but isn't any longer. In particular, gcc
>> in its default mode used to be based on C89/C90, but now is
>> based on C11.

Tell me when GCC fully supports C11 threading and atomics. So far, only
Pelles C does it. Well, it "kind of" supports the atomics:

https://forum.pellesc.de/index.php?topic=7311.0

https://forum.pellesc.de/index.php?topic=7167.0

damn.

Tim Rentsch

unread,
Aug 6, 2018, 8:11:48 AM8/6/18
to
C11 adds relatively few features that weren't in C previously.
However C99 was a significant change from C89/C90, including
among many other additions variable length arrays and variably
modified types, compound literals, and designated initializers.
VLAs/VMTs were made conditionally supported in C11 (but any
compiler that supports C99 is likely to support VLAs/VMTs in
C11). None of these three features is currently part of C++.
Designated initializers are on the list for inclusion in C++20;
there are AFAICT no plans to consider either VLAs/VMTs or
compound literals for later inclusion in C++.

I know there's a lot of overlap between post-1990 C and what
is available in C++, but I've given up trying to track exactly
what that is. Part of the problem there is C++ has constructs
that look like what is in C but have different semantics, so
it isn't enough to ask "does C++ have this?"; we also need to
ask "what does it do?". It seems easier to treat C and C++ as
being completely different languages rather than keep trying
to figure out exactly what's the same and what's different.

Thiago Adams

unread,
Mar 7, 2019, 1:17:10 PM3/7/19
to
On Tuesday, July 17, 2018 at 10:18:06 AM UTC-3, Thiago Adams wrote:
> What I am curious about, is how C++ implementations
> keep track of destructors that must be called for
> stack unwinding.
>
> For instance, if function F throws the destructor
> of X is called but the destructor of Y is not.
>
> int main()
> {
> X x;
> F();
> Y y;
> }
>
> One way I can imagine is to have an state machine
> and annotate the state after constructor of X.
> and to call the destructors depending on what was
> the final state before exception.
>
> Other way that I guess is simple is to have something
> like a linked list of destructors and build this list
> after each constructor success.

Some news about the subject.

https://devblogs.microsoft.com/cppblog/making-cpp-exception-handling-smaller-x64/

0 new messages