__attribute__((cleanup(function)) versus try/finally
Jason Merrill
jason@redhat.com
Thu May 8 19:37:00 GMT 2003
On 08 May 2003 11:40:21 -0700, Mark Mitchell <mark@codesourcery.com> wrote:
> If we were designing a new language, I'd certainly agree with you that
> exceptions are a good feature. When discussing C on a workstation-class
> machine, I'd be more likely to agree with you, even though I think
> adding exceptions to C is antithetical of that language's key design
> goals.
>
> When discussing C for embedded systems, though, I just can't see it. I
> fully concede that my scheme is less beautiful -- unless you see beauty
> in the number of bytes saved.
I argued in my last reply that sjlj EH can have the same cost as the
existing pthread cleanup mechanism. Do you disagree?
> I think what really happened here was that the pthreads designers wanted
> to add pthread_atexit -- and then got carried away.
I think they wanted exactly what they wrote. atexit isn't useful for
cleanups of a fixed duration.
> (In fact, so far as I can tell, a careful reading of
>
> http://www.unix.org/single_unix_specification/
>
> would suggest that:
>
> pthread_cleanup_push (f, a);
> goto l;
> pthread_cleanup_pop (1);
> l:
>
> is well-defined, and does not result in the cleanup being executed.
> Presumably, this should be undefined behavior, as it does not work at
> all with the sample implementation in the specification.
Agreed.
> The question of whether pthread_cleanup_push should create a new scope
> is also important, at least in C99 and C++: is
>
> int i;
> pthread_cleanup_push (f, a);
> int i;
>
> valid, or not, or is this unspecified?)
I'd say unspecified or valid. The spec talks about implementation as
macros containing { and }.
> Also, doesn't the execute argument to pthread_cleanup_pop mean that
> try/finally isn't the right construct?
>
> I would think that the EH version of:
>
> pthread_cleanup_push (f, a);
> g ();
> pthread_cleanup_pop (x);
>
> would be:
>
> try {
> g();
> } catch (...) {
> f(a);
> throw;
> }
> if (x) f(a);
Hard to do that with macros; you need to store the push args into temporary
variables. You'd need to do something like
{
void (*_f)(void *) = f;
void *_a = a;
try {
g();
} catch (...) {
_f (_a);
throw;
}
if (x) _f (_a);
}
but nobody's advocating adding catch to C, are they? Besides,
catch/rethrow is more expensive than running a cleanup.
A try/finally implementation would look like
{
void (*_f)(void *) = f;
void *_a = a;
int _x = 1;
try {
g ();
_x = x;
} finally {
if (_x) _f (_a);
}
}
An implementation using C++ destructors would work similarly:
struct cleanup {
void (*_f)(void *);
void *_a;
bool _x;
cleanup (void (*fn)(void *), void *arg): _f (fn), _a (arg), _x (true) {}
~cleanup () { if (_x) _f(_a); }
};
...
{
cleanup c (f, a);
g ();
c._x = x;
}
Or, using attribute (cleanup):
struct cleanup {
void (*_f)(void *);
void *_a;
int _x;
};
void cleanup_dtor (struct cleanup *p)
{
if (p->_x) (p->_f)(p->_a);
}
...
{
struct cleanup c __attribute ((__cleanup (cleanup_dtor)))
= { f, a, 1 };
g ();
c._x = x;
}
Any of these would do the job. All of them involve adding EH to C.
I think that try/finally is the most elegant way to do that.
Jason
More information about the Gcc
mailing list