__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