How to exit out of a function ? what is try-catch-throw in terms of Program Counter

J

Joel Yliluoma

NOTE: I am really afraid of try-catch-throw. I have never been
able to understand it since it does not exist in C and I cant
really visualize the construct in terms of C.

How try-catch-throw is actually implemented depends on the compiler,
but one can explain it like this.

Assume the following C++ code is written:

#include <cstdio>

// here's a sample object with a constructor and destructor
// to demonstrate scope.
class someobj
{
public:
someobj() { std::puts("constructor"); }
~someobj() { std::puts("destructor"); }
private:
int x; // a dummy member variable
};

// A dummy type, it could be a typedef of int or whatever.
// Just for the purpose of throwing an exception of this particular type.
struct someexceptiontype
{
};

void code_that_may_throw()
{
someobj obj; // instantiating someobj in this scope.

if(false != true)
{
// some error situation happened, throw an exception.
// someexceptiontype() instantiates an object of
// "someexceptiontype" (without binding it into a variable),
// and throw throws it.
throw someexceptiontype();
}

std::puts("wow, false is true");
}

void some_intermediate_function()
{
std::puts("1");
code_that_may_throw();
std::puts("2");
}

int main()
{
try
{
some_intermediate_function();
std::puts("executed without hitch");
}
catch(int e)
{
std::puts("caught an int");
}
catch(someexceptiontype e)
{
std::puts("caught someexceptiontype");
}
std::puts("end of main()");
return 0;
}

The code above contains high-level concepts that approximately translate
to the following lower-level concepts in C. It could be implemented
differently, but the function is the same.

#include <stdio.h>

typedef struct someobj
{
int x;
} someobj;

void someobj__construct(someobj* this)
{
puts("constructor");
if(__system_exception_ptr) goto __scope_end;
__scope_end: ;
}
void someobj__destruct(someobj* this)
{
puts("destructor");
if(__system_exception_ptr) goto __scope_end;
__scope_end: ;
}

struct someexceptiontype
{
};

/*** This global code is defined in some system library by the compiler */
void* __system_exception_ptr = (void*)0;
int __system_exception_type = 0;
void __clear_exception()
{
__system_exception_type = 0;
free(__system_exception_ptr);
__system_exception_ptr = (void*)0;
}
/*** End of compiler library code */

void code_that_may_throw(void)
{
someobj obj; // instantiating someobj in this scope.
someobj__construct(&obj);
if(__system_exception_ptr) goto __scope_end_before_obj;

if(0 != 1)
{
someexceptiontype* e = (someexceptiontype*) malloc(sizeof(*e));
__system_exception_ptr = e;
__system_exception_type = 2;
/* ^ a compiler-specific tag that identifies the exception type */
goto __scope_end;
}

puts("wow, false is true");
if(__system_exception_ptr) goto __scope_end;

__scope_end: ;
someobj__destruct(&obj);
__scope_end_before_obj: ;
}

void some_intermediate_function(void)
{
puts("1");
if(__system_exception_ptr) goto __scope_end;

code_that_may_throw();
if(__system_exception_ptr) goto __scope_end;

puts("2");
if(__system_exception_ptr) goto __scope_end;
__scope_end: ;
}

int main(void)
{
some_intermediate_function();
if(__system_exception_ptr) goto try_catch;
puts("executed without hitch");
if(__system_exception_ptr) goto try_catch;
goto past_catch;
try_catch: ;
switch(__system_exception_type)
{
case 1: /* example denoting int type */
{
__clear_exception();
puts("caught an int");
if(__system_exception_ptr) goto __scope_end;
break;
}
case 2: /* example denoting someexceptiontype */
{
__clear_exception();
puts("caught someexceptiontype");
if(__system_exception_ptr) goto __scope_end;
break;
}
default:
goto __scope_end; /* still not caught */
}
past_catch: ;
puts("end of main()");
if(__system_exception_ptr) goto __scope_end;

__scope_end: ;
return 0;
}

Of course, for efficiency reasons there is no "if" test after every
function return for exceptions (rather, execution may be transferred
to a dedicated stack and scope unfolder when an exception happens),
but this was the easiest way to explain what happens as regards for
scopes and execution paths.
Also, in the exception handler (catch {}), the exception object is
not supposed to be deallocated until the end of the handler, but
for simplicity I wrote the deallocation first.

Followups set to comp.lang.c++ .
 
S

Stefan Monnier

NOTE: I am really afraid of try-catch-throw. I have never been
able to understand it since it does not exist in C and I can't
really visualize the construct in terms of C. That is what my

Actually, these constructs pretty much exist in C as well: `catch' is called
`setjmp', and `throw' is called `longjmp'.


Stefan
 
G

gnuist006

Actually, these constructs pretty much exist in C as well: `catch' is called
`setjmp', and `throw' is called `longjmp'.

Stefan

Is it in some obscure corner of K&R ANSI ? I was never taught this one
by my instructor. Can you explain its syntax and patterns of usage ?
 
V

Victor Bazarov

Stefan said:
Actually, these constructs pretty much exist in C as well: `catch' is
called `setjmp', and `throw' is called `longjmp'.

I believe a better way would be to imagine that 'try', not 'catch',
is called 'setjmp'.

V
 
G

gnuist006

Actually, these constructs pretty much exist in C as well: `catch' is called
`setjmp', and `throw' is called `longjmp'.

Stefan

Stefan, let me thank you for what seems to me to be the correct
concept.
I searched this whole thread in google for setjmp and YOU are the only
one who mentioned it. I applaud you. Because, it does not seem that
there
is any other construct that can implement try-catch-throw. I still
have to
read up on it, but thats what my gut instinct says.

Anyone, care to show how this translates into assembly after we deal
thoroughly with this in the context of C ?

Everyone, please ignore the the mean spirits trying to derail a
serious
conceptual discussions and calling each other trolls or giving
obfuscated
explanations for ego purposes, and not LUCID explanation.
 
A

Alf P. Steinbach

* (e-mail address removed):
Stefan, let me thank you for what seems to me to be the correct
concept.
I searched this whole thread in google for setjmp and YOU are the only
one who mentioned it.

Uh, have you plonked me, then?

I think that my reply was the very first reply in the thread.

I suggest you read that article again, because it contains some
important details not mentioned by Stefan et.al.


- Alf
 
G

gnuist006

* (e-mail address removed):








C++ does not have a built-in 'exit' command. There is a library
function 'exit' which exits the process. One must assume that's not
what you mean, and that you're not asking C and C++ programmers to teach
you Lisp.

Therefore, assuming you want to exit the function or the block.


It's not a general class of problem.

Appropriate solutions depend on the problem at hand.

E.g., in C++,

// (if (test) (exit) (do something))

void foo()
{
if( !test )
{
doSomething();
}
}

void bar()
{
if( test ) { return; }
doSomething();
}


The closest equivalent in C would be a 'longjmp'. However, a C++
exception is more limited, in that it will only jump up the call chain,
and it's more powerful, in that it will destroy local objects as it does
so. Also, if you use 'longjmp' in C++ you're practically doomed (unless
you use it to jump between co-routines with their own stacks), because
'longjmp' doesn't destroy local objects.

Sure you have good ideas.

I still would like an equivalent implementation explained. Ofcourse,
smart
companies and smart programmers were doing all this before C++ came
and even in LISP they have atleast two of try catch throw.
 
A

Alf P. Steinbach

* (e-mail address removed):
Sure you have good ideas.

Note that some people could read that as an attempt at insulting.

I still would like an equivalent implementation explained. Ofcourse,
smart
companies and smart programmers were doing all this before C++ came
and even in LISP they have atleast two of try catch throw.

Ada introduced to the wider community much that was subsequently adopted
in C++. Interestingly, (much of) the STL was implemented in Ada before
it was implemented in C++. And perhaps also interestingly, Ada's high
level thread primitives are seemingly now /not/ considered for C++.

Now as for equivalence, you don't really want C code, because that would
have to emulate C++ objects!

But in C++ such longjm-based code is hairy compiler-dependent stuff,
with formally Undefined Behavior.

Also, as an example of equivalent-except-for-efficiency, note that a
call of a virtual function can be specified equivalently as a dynamic
lookup in most derived class, base class, base class' base class and so
on, a search up the base class chain, but is in actuality implemented as
a table look-up (with all compilers). Exceptions are implemented in
more than just one main way. However, analogously to the case with
virtual functions, equivalent code that performs dynamic lookup, such as
the code below, is extremely ineffecient compared to the Real Thing(TM).

Depending on the actual implementation of exceptions, there can be no
overhead at all for normal case code.


<code>
#include <vector>
#include <csetjmp>
#include <string>
#include <iostream>
#include <ostream>

#if defined( _MSC_VER )
# define LNGJMP_DESTROYS_AUTOMAGICALLY

#elif defined( __GNUC__ )
# undef LNGJMP_DESTROYS_AUTOMAGICALLY
# // No automatic destruction, at least in MingW 3.4.4 version.

#else
# error Your compiler is not supported by this program.
#endif

struct AbstractLngjmpCleanup
{
virtual ~AbstractLngjmpCleanup() {}
virtual void destroy() = 0;
};

template< typename T >
struct LngjmpCleanup: AbstractLngjmpCleanup
{
T* myTarget;
LngjmpCleanup( T& target ): myTarget( &target ) {}
virtual void destroy()
{
#ifndef LNGJMP_DESTROYS_AUTOMAGICALLY
myTarget->T::~T(); // Call destructor on target.
#endif
}
};

struct LongjmpCleanups
{
std::vector<AbstractLngjmpCleanup*> myDestroyers;

~LongjmpCleanups()
{
for( size_t i = 0; i < myDestroyers.size(); ++i )
{
delete myDestroyers.at( i );
}
}

template< typename T >
void add( T& target )
{
myDestroyers.push_back( new LngjmpCleanup<T>( target ) );
}

void destroyAll()
{
for( size_t i = 0; i < myDestroyers.size(); ++i )
{
myDestroyers.at( i )->destroy();
}
}
};

template< typename T >
void say( T const& v ) { std::cout << v << std::endl; }

struct Whatever
{
std::string myId;
Whatever( std::string id ): myId( id )
{ say( "Constructed " + myId + "." ); }

~Whatever()
{ say( "Destroyed " + myId + "." ); }
};


jmp_buf* pReturnAddress = 0;

void bottom()
{
LongjmpCleanups destroyers;
LngjmpCleanup<LongjmpCleanups> destroyersDestroyer( destroyers );

Whatever localObject( "bottom()'s local object" );

destroyers.add( localObject );

say( "Executing body of bottom()." );

say( "Throwing simulated exception." );
{
destroyers.destroyAll();
destroyersDestroyer.destroy();
longjmp( *pReturnAddress, 1 );
}
}

void middle()
{
jmp_buf returnAddress;
jmp_buf* pOldReturnAddress;
LongjmpCleanups destroyers;
LngjmpCleanup<LongjmpCleanups> destroyersDestroyer( destroyers );

Whatever localObject( "middle()'s local object" );

destroyers.add( localObject );
pOldReturnAddress = pReturnAddress;
if( setjmp( returnAddress ) == 0 )
{
pReturnAddress = &returnAddress;

say( "Executing body of middle(), calling bottom()." );
bottom();

pReturnAddress = pOldReturnAddress;
}
else
{
destroyers.destroyAll();
destroyersDestroyer.destroy();
pReturnAddress = pOldReturnAddress;
longjmp( *pReturnAddress, 1 );
}
}

void top()
{
jmp_buf returnAddress;
jmp_buf* pOldReturnAddress;
LongjmpCleanups destroyers;
LngjmpCleanup<LongjmpCleanups> destroyersDestroyer( destroyers );

Whatever localObject( "top()'s local object" );

destroyers.add( localObject );
pOldReturnAddress = pReturnAddress;
if( setjmp( returnAddress ) == 0 )
{
pReturnAddress = &returnAddress;

say( "Executing body of top(), calling middle()." );
middle();

pReturnAddress = pOldReturnAddress;
}
else
{
destroyers.destroyAll();
destroyersDestroyer.destroy();
pReturnAddress = pOldReturnAddress;
longjmp( *pReturnAddress, 1 );
}
}

int main()
{
jmp_buf returnAddress;

pReturnAddress = &returnAddress;
if( setjmp( returnAddress ) == 0 )
{
say( "Main business code, calling top()..." );
top();
return EXIT_SUCCESS;
}
else
{
say( "Caught simulated exception!" );
return EXIT_FAILURE;
}
}
</code>

<output>
Main business code, calling top()...
Constructed top()'s local object.
Executing body of top(), calling middle().
Constructed middle()'s local object.
Executing body of middle(), calling bottom().
Constructed bottom()'s local object.
Executing body of bottom().
Throwing simulated exception.
Destroyed bottom()'s local object.
Destroyed middle()'s local object.
Destroyed top()'s local object.
Caught simulated exception!
</output>

Now I leave it as an exercise to reimplement this program to use C++
exceptions instead of longjmp, and perhaps compare clarity (and
efficiency, if that's interesting).

Cheers, & hth.,

- Alf
 
J

Joel Yliluoma

Actually, these constructs pretty much exist in C as well:
`catch' is called `setjmp', and `throw' is called `longjmp'.

If you ignore the thing about scope that I was being very careful
to illustrate properly, then yes.
And, the fact that try-catch blocks can be nested, recursed, etc,
and only catching the matching type of exception stops the unwinding.
 
S

Stefan Monnier

NOTE: I am really afraid of try-catch-throw. I have never been
I believe a better way would be to imagine that 'try', not 'catch',
is called 'setjmp'.

Sorry, I'm reading this on gnu.emacs.help where Elisp only provides `catch'
and `throw' (no `try') and these map pretty closely to setjmp/longjmp.


Stefan
 
S

Stefan Monnier

Anyone, care to show how this translates into assembly after we deal
thoroughly with this in the context of C ?

I believe that one way to look at setjmp/longjmp in C is that setjmp saves
a copy of the registers (most importantly PC and SP) and longjmp uses that
copy to jump back to the corresponding point in the program (and stack
activation).


Stefan
 
D

David Thompson

* (e-mail address removed):

The closest equivalent in C would be a 'longjmp'. However, a C++
exception is more limited, in that it will only jump up the call chain,

C longjmp/setjmp also is only guaranteed to work up the stack; the
fact that _some_ implementations can work cross-stack and in
particular cross-thread is not standard nor portable.
and it's more powerful, in that it will destroy local objects as it does
so. Also, if you use 'longjmp' in C++ you're practically doomed (unless
you use it to jump between co-routines with their own stacks), because
'longjmp' doesn't destroy local objects.
Actually it's Undefined Behavior; a good quality C++ implementation
CAN coordinate longjmp, and also pthreads cancellation, with
exceptions to destruct locals cleanly -- but it's not required.

- formerly david.thompson1 || achar(64) || worldnet.att.net
 
A

Alf P. Steinbach

* David Thompson:
C longjmp/setjmp also is only guaranteed to work up the stack; the
fact that _some_ implementations can work cross-stack and in
particular cross-thread is not standard nor portable.

So?

But also, what on Earth do you mean by a cross-thread longjmp? I
implemented coroutines in terms of longjmp at the time that was popular,
so the concepts involved are not unfamiliar to me. Yet I fail to
envision what you could be talking about, especially as "fact". I think
perhaps you're talking about restoring the full context (registers etc)
of a moment in a thread's execution?

Actually it's Undefined Behavior; a good quality C++ implementation
CAN coordinate longjmp, and also pthreads cancellation, with
exceptions to destruct locals cleanly -- but it's not required.

I don't know about ptheads cancellation, but other than that you're
right. Visual C++ coordinates longjmp with C++ stack unwinding. g++,
on the other hand, does not.

Cheers, & hth.,

- Alf
 
A

Alf P. Steinbach

* Alf P. Steinbach:
* David Thompson:

So?

But also, what on Earth do you mean by a cross-thread longjmp? I
implemented coroutines in terms of longjmp at the time that was popular,
so the concepts involved are not unfamiliar to me. Yet I fail to
envision what you could be talking about, especially as "fact". I think
perhaps you're talking about restoring the full context (registers etc)
of a moment in a thread's execution?



I don't know about ptheads cancellation, but other than that you're
right. Visual C++ coordinates longjmp with C++ stack unwinding. g++,
on the other hand, does not.

Sorry, I didn't see the weasel-word "actually", which indicates a
contradiction.

When I wrote that you're right, that just meant that you supplied some
extra info that wasn't incorrect.

Cheers,

- Alf
 
D

David Thompson

* David Thompson:

So?

But also, what on Earth do you mean by a cross-thread longjmp? I
implemented coroutines in terms of longjmp at the time that was popular,
so the concepts involved are not unfamiliar to me. Yet I fail to
envision what you could be talking about, especially as "fact". I think

IME 'coroutine' has been used for several slightly different concepts,
but if you mean the one of separate threads of control passing CPU
ownership often along with data anytime they choose, also known more
specifically as cooperative/nonpreemptive threading/tasking, yes. I
think you are agreeing that it did actually work, because 'restoring'
PC and SP (or equivalents) was enough; but I am pointing out it wasn't
and isn't _required_ to work that way.
perhaps you're talking about restoring the full context (registers etc)
of a moment in a thread's execution?
IME a cooperative switch itself doesn't need to save and restore other
state, as the language mechanism(s) e.g. 'call yield' handle it. Or
for cache-y things it happens automatically, or mostly automatically.

- formerly david.thompson1 || achar(64) || worldnet.att.net
 
A

Alf P. Steinbach

* David Thompson:
IME 'coroutine' has been used for several slightly different concepts,
but if you mean the one of separate threads of control passing CPU
ownership often along with data anytime they choose, also known more
specifically as cooperative/nonpreemptive threading/tasking, yes. I
think you are agreeing that it did actually work, because 'restoring'
PC and SP (or equivalents) was enough; but I am pointing out it wasn't
and isn't _required_ to work that way.

IME a cooperative switch itself doesn't need to save and restore other
state, as the language mechanism(s) e.g. 'call yield' handle it. Or
for cache-y things it happens automatically, or mostly automatically.

Sorry, I fail to see the point, whatever it is.

But regarding definition of 'coroutine', it really doesn't map to more
than one concept.

Coroutines are treated in Knuth's TAOCPM, which locked in the
terminology (although Knuth didn't always succeed in in establishing
convention, e.g. he had to redraw his trees because he at first did them
with the root down, while the rest of the CS community chose root up,
and same for his misconception of "real time" as "reel time", he
couldn't make that stick either :) ); some languages, notably Modula-2,
had built-in support for coroutines; you can find some dicussion of
coroutines at <url: http://en.wikipedia.org/wiki/Coroutine>.

Cheers, & hth.,

- Alf
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,578
Members
45,052
Latest member
LucyCarper

Latest Threads

Top