7 ms·
I've never had to use 'goto' in C++ except to break from a nested loop. In C++ labeled breaks would make 'goto' completely obsolete. In C it would still have u
by api 11y ago
I've never had to use 'goto' in C++ except to break from a nested loop. In C++ labeled breaks would make 'goto' completely obsolete.
In C it would still have use in the implementation of orderly error handling -- the pattern where you hand-implement exception handling in C by putting an on_error: label at the end of the function that is goto'd on error. The addition of some orderly construct for this in C would eliminate that case, leaving no real role for goto there either.
- jasode 11y ago>In C++ labeled breaks would make 'goto' completely obsolete. In 20 years of using C/C++, I've found one use for "goto" that's hard to substitute: simulating coroutines that yield to an outer context. (Similar to C# yield return). A "break label" gets you out of a loop. I needed to use "goto" to jump back into the middle of a loop to resume where the "coroutine" previously left off. The keywords "break/setjmp/longjmp" wouldn't have been substitutes for this particular use case.
- prutschman 11y agoI've used a switch statement essentially as a jump table for this purpose. I'm curious whether that would have worked, or if you were doing something different enough that you needed a goto.
- IsTom 11y agoI don't quite understand how it's not a setjmp/longjmp usecase. Isn't this exactly what longjmp is for?
- frivoal 11y agoA bitecode interpreter is another place where it's nice to have gotos. Here's the base code without gotos: typedef enum { ADD, MUL, ..., END } opcode; void run() { opcode ins; while (1) { ins = fetch_next_inst(); switch (ins) { case ADD: perform_addition(); break; case MUL: perform_multiplication(); break; ... case END: wrap_up(); return; } } } You have 3 jumps on each loop. From the break to the end of the loop, then from the end to the top, and one from the switch to the right case. The first one might be optimized away, but let's remove it explicitly. typedef enum { ADD, MUL, ..., END } opcode; void run() { opcode ins; start: ins = fetch_next_inst(); switch (ins) { case ADD: perform_addition(); goto start; case MUL: perform_multiplication(); goto start; ... case END: wrap_up(); return; } } Assuming a non lousy compiler, we haven't improved anything yet. But now the fun starts. We can go down to one jump for each iteration. typedef enum { ADD, MUL, ..., END } opcode; #define NEXT() \ do { \ ins = fetch_next_inst(); \ goto *jump_table[ins]; \ } while(0) void run() { opcode ins; static void *jump_table[] = { &&add_l, &&mul_l, ..., &&end_l }; NEXT(); add_l: perform_addition(); NEXT(); mul_l: perform_multiplication(); NEXT(); ... end_l: wrap_up(); return; } Voila! a single jump every time around. Now, depending on what kind of architecture you're running on, the size of the cache, etc, this may or may not be faster. Granted, this is not the kind of code you write everyday. But sometimes speed matters, and good luck writing this without gotos.
- zem 11y ago~20 years of using various combinations of C and C++, and i never knew you could take the address of a label! reminds me of computed GOSUBs from my basic days :)
- frivoal 11y agoAnd they say C is a simple language... :) To be fair, I've seen code like this exactly once.
- jcoffland 11y agoWho says that?
- tjradcliffe 11y agoThey say C is a low level language. That is not the same as simple.
- banthar 11y agoYou can't. This is a GCC extension: https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html
- arcticbull 11y agoThere is actually a neat feature where you can take the address of the thing you're assigning in a static context: static void* address = &address; This is helpful for things that take void* context pointers that you want to match with logical equality.
- jacquesm 11y agoBefore you use such constructs make sure your team lead or boss is ok with it, it's incompatible with many compilers and ugly to boot. This is the kind of code that gives C its bad reputation.
- sklogic 11y ago
- Niten 11y ago> The addition of some orderly construct for this in C would eliminate that case, leaving no real role for goto there either. I like the way this is handled in Go with the defer keyword: https://blog.golang.org/defer-panic-and-recover https://blog.golang.org/defer-panic-and-recover This construct gives you most of the power of C++ RAII without the overhead. Except that you can't use it to cleanup resources after exiting an anonymous block—it strictly defers to function exit.
- pcwalton 11y agodefer has unavoidable runtime overhead due to its dynamic semantics. It's strictly slower than RAII as implemented in C++ or Rust.
- giovannibajo1 11y agoThat's not true. The only case where the runtime overhead is unavoidable is calling defer in a loop, because it might require allocating the defer chain in the heap; in all other cases, it's just a metter of writing the correct optimization passes in the compiler.
- pcwalton 11y agoYes, that's what I meant by "strictly slower". At best, it can be optimized to something similar to what RAII can give you. RAII never has the overhead of the bad case.
- giovannibajo1 11y agoI don't want to be excessively picky, but you said that is has "unavoidable runtime overhead", and that's true only in its rarest form (defer within a loop), which is a feature which you can't implement in RAII. IOW, defer is a superset of RAII. In all other cases (which is almost all usages), it is semantically equivalent to RAII, so the language doesn't force any runtime overhead. The only difference is that the compiler is less mature than an average C++ compiler, but this is an implementation problem, not a design problem. RAII has no overhead because it is a pattern designed within the context of a zero-overhead language. defer allows you to implement a superset of cases that RAII handles, including those with runtime overhead.