Live data from Hacker News

Defer available in gcc and clang

gustedt.wordpress.com

111–120 of 262 posts

Re: Defer available in gcc and clang

#111
In C I just used goto - you put a cleanup section at the bottom of your code and your error handling just jumps to it.

  #define RETURN(x) result=x;goto CLEANUP

  void myfunc() {
    int result=0;
    if (commserror()) {
      RETURN(0);
    }
     .....
    /* On success */
    RETURN(1);

    CLEANUP:
    if (myStruct) { free(myStruct); }
    ...
    return result
  }
The advantage being that you never have to remember which things are to be freed at which particular error state. The style also avoids lots of nesting because it returns early. It's not as nice as having defer but it does help in larger functions.

Re: Defer available in gcc and clang

#112
post #102

Earlier quoted context omitted.

People manually doing resource cleanup by using goto. I'm assuming that using defer would have prevented the gotos in the first case, and the bug.

Is that true though? Using defer, the code would be: if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0) return err; return err; This has the exact same bug: the function exits with a successful return code as long as the SHA hash update succeeds, skipping further certificate validity checks. The fact that resource cleanup has been relegated to defer so that 'goto fail;' can be replaced with 'return err;' f…

It would have resulted in an uninitialized variable access warning, though.

Re: Defer available in gcc and clang

#113
post #97

Earlier quoted context omitted.

This certainly isn't RAII—the term is quite literal, Resource Acquisition Is Initialization, rather than calling code as the scope exits. This is the latter of course, not the former.

People often say that "RAII" is kind of a misnomer; the real power of RAII is deterministic destruction. And I agree with this sentiment; resource acquisition is the boring part of RAII, deterministic destruction is where the utility comes from. In that sense, there's a clear analogy between RAII and defer. But yeah, RAII can only provide deterministic destruction because resource acquisition is initialization. As lo…

In my opinion, it's the initialization part of RAII which is really powerful and still missing from most other languages. When implemented properly, RAII completely eliminates a whole class of bugs related to uninitialized or partially initialized objects: if all initialization happens during construction, then you either have a fully initialized correct object, or you exit via an exception, no third state. Additionaly, tying resources to constructors makes the correct order of freeing these resources automatic. If you consume all your dependencies during construction, then destructors just walk the dependency graph in the correct order without you even thinking about it. Agreed, that writing your code like this requires some getting used to and isn't even always possible, but it's still a very powerful idea that goes beyond simple automatic destruction

Re: Defer available in gcc and clang

#114

Earlier quoted context omitted.

People manually doing resource cleanup by using goto. I'm assuming that using defer would have prevented the gotos in the first case, and the bug.

To be fair, there were multiple wrongs in that piece of code: avoiding typing with the forward goto cleanup pattern; not using braces; not using autoformatting that would have popped out that second goto statement; ignoring compiler warnings and IDE coloring of dead code or not having those warnings enabled in the first place. C is hard enough as is to get right and every tool and development pattern that helps avoid…

The forward goto cleanup pattern is not something "wrong" that was done to "avoid typing". Goto cleanup is the only reasonable way I know to semi-reliably clean up resources in C, and is widely used among most of the large C code bases out there. It's the main way resource cleanup is done in Linux.

By putting all the cleanup code at the end of the function after a cleanup label, you have reduced the complexity of resource management: you have one place where the resource is acquired, and one place where the resource is freed. This is actually manageable. Before you return, you check every resource you might have acquired, and if your handle (pointer, file descriptor, PID, whatever) is not in its null state (null pointer, -1, whatever), you call the free function.

By comparison, if you try to put the correct cleanup functions at every exit point, the problem explodes in complexity. Whereas correctly adding a new resource using the 'goto cleanup' pattern requires adding a single 'if (my_resource is not its null value) { cleanup(my_resource) }' at the end of the function, correctly adding a new resource using the 'cleanup at every exit point' pattern requires going through every single exit point in the function, considering whether or not the resource will be acquired at that time, and if it is, adding the cleanup code. Adding a new exit point similarly requires going through all resources used by the function and determining which ones need to be cleaned up.

C is hard enough as it is to get right when you only need to remember to clean up resources in one place. It gets infinitely harder when you need to match up cleanup code with returns.

Re: Defer available in gcc and clang

#115
post #102

Earlier quoted context omitted.

Is that true though? Using defer, the code would be: if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0) return err; return err; This has the exact same bug: the function exits with a successful return code as long as the SHA hash update succeeds, skipping further certificate validity checks. The fact that resource cleanup has been relegated to defer so that 'goto fail;' can be replaced with 'return err;' f…

It would have resulted in an uninitialized variable access warning, though.

I don't think so. The value is set in the assignment in the if statement even for the success path. With and without defer you nowadays get only a warning due to the misleading indentation: https://godbolt.org/z/3G4jzrTTr (updated)

Re: Defer available in gcc and clang

#116
post #111

In C I just used goto - you put a cleanup section at the bottom of your code and your error handling just jumps to it. #define RETURN(x) result=x;goto CLEANUP void myfunc() { int result=0; if (commserror()) { RETURN(0); } ..... /* On success */ RETURN(1); CLEANUP: if (myStruct) { free(myStruct); } ... return result } The advantage being that you never have to remember which things are to be freed at which particular…

One small nitpick: you don't need check before `free` call, using `free(NULL)` is fine.

Re: Defer available in gcc and clang

#117
post #102

Earlier quoted context omitted.

Is that true though? Using defer, the code would be: if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0) return err; return err; This has the exact same bug: the function exits with a successful return code as long as the SHA hash update succeeds, skipping further certificate validity checks. The fact that resource cleanup has been relegated to defer so that 'goto fail;' can be replaced with 'return err;' f…

It would have resulted in an uninitialized variable access warning, though.

No it wouldn't. 'err' is declared and initialized at the start of the function. Even if it wasn't initialized at the start, it would've been initialized by some earlier fallible function call which is also written as 'if ((err = something()) != 0)'

Re: Defer available in gcc and clang

#118
post #71

Can somebody explain why this is significantly better than using goto pattern? Genuinely curious as I only have a small amount of experience with c and found goto to be ok so far

I feel like C people, out of anyone, should respect the code gen wins of defer. Why would you rely on runtime conditional branches for everything you want cleaned up, when you can statically determine what cleanup functions need to be called? In any case, the biggest advantage IMO is that resource acquisition and cleanup are next to each other. My brain understands the code better when I see "this is how the resource…

>"this is how the resource is acquired, this is how the resource will be freed later"

Lovely fairy tale. Now can you tell me how you love to scroll back and examine all the defer blocks within a scope when it ends to understand what happens at that point?

Re: Defer available in gcc and clang

#119
post #7

It’s pedantic, but in the malloc example, I’d put the defer immediately after the assignment. This makes it very obvious that the defer/free goes along with the allocation. It would run regardless of if malloc succeeded or failed, but calling free on a NULL pointer is safe (defined to no-op in the C-spec).

3…2…1… and somebody writes a malloc macro that includes the defer.

Re: Defer available in gcc and clang

#120
post #111

In C I just used goto - you put a cleanup section at the bottom of your code and your error handling just jumps to it. #define RETURN(x) result=x;goto CLEANUP void myfunc() { int result=0; if (commserror()) { RETURN(0); } ..... /* On success */ RETURN(1); CLEANUP: if (myStruct) { free(myStruct); } ... return result } The advantage being that you never have to remember which things are to be freed at which particular…

One small nitpick: you don't need check before `free` call, using `free(NULL)` is fine.

But it does keep one in the habit of using NULL checks.
Post reply on HN