Live data from Hacker News

GOTOphobia considered harmful in C

blog.joren.ga

61–70 of 319 posts

Re: GOTOphobia considered harmful in C

#61
I'm browsing this, and I'm not seeing the way I do it, which is sort-of-like #5 but not quite... I tend to wrap the code that has multiple exits-to-label in a do...while(0) loop, and use break to get there...

So it might look like:

  do {
    if (false == call_func1()) {
      cleanup_any_state();
      break;
    }

    if (false == call_func2()) {
      cleanup_any_state();
      break;
    }

  } while (0);
At any point you can branch to the common exit-statement, and keep on testing for failure as you go through the algorithm without indenting forever.

Generally there's no too much state to clean up in the code I've been using this in, but obviously later 'break' conditions would have to clean up the state for earlier ones too. That's easy to abstract into functions for the cleanup though.

Re: GOTOphobia considered harmful in C

#62
post #47

The C version: int* foo(int bar) { int* return_value = NULL; if (!do_something(bar)) goto error_1; if (!init_stuff(bar)) goto error_2; if (!prepare_stuff(bar)) goto error_3; return_value = do_the_thing(bar); error_3: cleanup_3(); error_2: cleanup_2(); error_1: cleanup_1(); return return_value; } The D version: int* foo(int bar) { scope(exit) cleanup1(); if (!do_something(bar)) return null; scope(exit) cleanup2(); if…

I have a distaste for all of these examples, which comes from the existence of a side-effecting operation: calling do_something() necessitates the need to call a cleanup function, which means there's some state being changed but hidden behind the internals of these methods. It is really easy to call this incorrectly which says to me it's just a badly-designed API. In C# the idiomatic way would be to have each of thes…

In C# it's customary just to wave away the worst kinds of problem that C and D developers try to handle, and let the runtime kill your program. (This is more an artifact of why people pick their languages than anything inherent on the languages themselves.)

But rest assured, your C# code is full of global state hidden on its runtime and is subject to the same kinds of errors people are discussing here.

Re: GOTOphobia considered harmful in C

#63
post #24

Earlier quoted context omitted.

Go doesn't have RAII or destructors, it has defer.

Which definitely does weird things in a loop.

IIFEs trivially solve that.

tbh I see FAR more range-var-misuse with Go loops than defers. I've seen over 100 range-var problems, and seen lints catch many more (several thousand), but I've only seen a loop defer issue once. When a defer is needed, it seems like people both remember the issue better (`defer` is a new construct for many, `for` is not and habits from other languages can mislead them), and the code is complex enough to justify a helper func, where a defer is trivially correct.

Re: GOTOphobia considered harmful in C

#64
post #53

Earlier quoted context omitted.

That error is not due to goto, it was just a goto which was errously executed because of badly formatted code. (It looked like the statement was inside an if-block due to the indent.) Pyhon would have prevented this bug, but so would a formatter. Rust also requires braces for if-blocks to prevent this kind of error.

"The problem isn't C, it's that they weren't using C properly..."

That's not really a fair complaint against C, when many safer languages whose syntax derives from C (e.g. javascript) would have the same problem.

Re: GOTOphobia considered harmful in C

#65
This blogpost is horrible! The title is good, you can tell if someone actually uses C at a decent level based off of if they describe SESE(single exit single entry) and how you use goto's to achieve that.

BUT, the fact they have multiple goto locations in one function violates this! Only one goto locations ! That goto is goto cleanup, or goto exit. What you do is then check state of each variable you cleanup. Every function should be some variable of this. If anyone writes C in any other style than SESE, you can consider them a subpar C programmer. There's variations like using BOOL and in and out variables. I like them, but there are different styles. But anyone not using a single AND ONLY A SINGLE goto in every function is 100% a subpar C programmer who you should not trust.

    BOOL foo()
    {
       int *allocation;
       char *allocation2;
       BOOL bRet = FALSE;
       const int BUFFSIZE = 10; //NO MAGIC NUMBERS
       allocation = resourceallocation(BUFFSIZE); // Malloc, file.open, network open, etc
       if(!allocation)
       {
          DEBUGPRINT("ALLOCAITON FAILED");
          bRet = FALSE; //Redundent, but protect against intern
          goto cleanup;
       }

       allocation2 = resourceallocation2(BUFFSIZE); // Malloc, file.open, network open, etc
       if(!allocation2)
       {
          DEBUGPRINT("ALLOCAITON FAILED");
          bRet = FALSE; //Redundent, but protect against intern
          goto cleanup;
       }
    
       ...
       bRet = TRUE;
    cleanup:
       //Add error handling if allocation fails
       if(allocation)
         resourcefree(allocation);
       if(allocation2)
         resourcefree(allocation2);

    
       return bRet;
    }

Re: GOTOphobia considered harmful in C

#66

Earlier quoted context omitted.

You can totally throw an exception from RAII code if you declare the destructor noexcept(false). Goto + RAII isn’t generally compatible unless you structure things very carefully

You can, and your program will call std::terminate if there’s already an exception being processed. Not exactly desirable if you’re trying to write code that ensures careful resource cleanup. Also why it’s widely regarded as _wrong_ to ever throw in a destructor.

IMO this is a design bug in C++. The authors couldn't agree on what to do in the exception-during-unwind scenario, so they chose the worst possible option: crash.

In most cases, an second exception raised while another exception is already being thrown is merely a side-effect of the first exception, and can probably safely be ignored. If the idea of throwing away a secondary exception makes you uncomfortable, then another possible solution might have been to allow secondary exceptions to be "attached" to the primary exception, like `std::exception::secondary()` could return an array of secondary exceptions that were caught. Obviously there's some API design thought needed here but it's not an unsolvable problem.

If we could just change C++ to work this way, then throwing destructors would be no problem, it seems? So this seems like a C++-specific problem, not fundamental to RAII.

That said, there is another camp which argues that it fundamentally doesn't make sense for teardown of resources to raise errors. I don't think you're in this camp, since you were arguing the opposite up-thread. I'm not in that camp either.

Re: GOTOphobia considered harmful in C

#67
post #24

In RAII languages[0], you obviously don't need unrestricted gotos. However, I always find myself missing it when writing nested loops. Labeled break and continue[1] ought to be considered standard structure programming primitives. These are restricted gotos and allowing them to break or continue a parent loop doesn't unrestrict them much. But it does significantly improve the expressive power of your looping construc…

Go doesn't have RAII or destructors, it has defer.

defer proposal for C made in 2021 https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2895.htm

... why isn't this in C already?

Re: GOTOphobia considered harmful in C

#68

In RAII languages[0], you obviously don't need unrestricted gotos. However, I always find myself missing it when writing nested loops. Labeled break and continue[1] ought to be considered standard structure programming primitives. These are restricted gotos and allowing them to break or continue a parent loop doesn't unrestrict them much. But it does significantly improve the expressive power of your looping construc…

> Labeled break and continue ...

I find that normally if I need nested break then it suffices to refactor the target loop into a function and use return instead.

I don't think I normally miss multilevel continue, but the same strategy would work for that too. You'd just pull out the target loop body rather than the whole loop.

If that doesn't work because you need to select too many different break/continue levels (more than two) then maybe it's time to review the complexity of the function anyway.

Re: GOTOphobia considered harmful in C

#69

In RAII languages[0], you obviously don't need unrestricted gotos. However, I always find myself missing it when writing nested loops. Labeled break and continue[1] ought to be considered standard structure programming primitives. These are restricted gotos and allowing them to break or continue a parent loop doesn't unrestrict them much. But it does significantly improve the expressive power of your looping construc…

> In RAII languages[0], you obviously don't need unrestricted gotos.

You don't need them for teardown, but they still make sense for retries -- cases where a procedure needs to start over after hitting certain branches, e.g. a transaction conflict. I think `goto retry` is a lot more readable than wrapping the procedure in `do { ... } while (false)` and using `continue` to retry.

`goto` works very nicely with RAII here in that it'll invoke the destructors of any local variables that weren't declared yet at the point being jumped back to.

Re: GOTOphobia considered harmful in C

#70
post #53

Earlier quoted context omitted.

That error is not due to goto, it was just a goto which was errously executed because of badly formatted code. (It looked like the statement was inside an if-block due to the indent.) Pyhon would have prevented this bug, but so would a formatter. Rust also requires braces for if-blocks to prevent this kind of error.

"The problem isn't C, it's that they weren't using C properly..."

But that is a legitimate problem.

Using a language feature that is a known footgun (`if ...` instead of `if {...}`) without being cautious enough to avoid shooting yourself in the foot is not the fault of the footgun, it's the fault of the programmer.

Additionally, in the above linked case, the problem isn't a misused `goto`, it's a misused `if ...`. It would be just as problematic if they typed `cleanup_context();` instead of `goto fail;`, but nobody complains about cleaning up state, do they?

Post reply on HN