Ode to Goto

Posted on Jul 20, 2021

Oh! Goto, thou who yieldeth immense strength! OK, not a literal ode. But, yes I’d love to take some time to give Goto a moment of appreciation. Goto is so vilified, yet it’s undeserved. The Culprit of Spaghetti Code? Nope! In fact, it might even save you from your code becoming a plate of pasta. Today, your lovely Ariadna is going to (pun intended) demystify Goto for ya.

If you don’t like Goto, you don’t like Turing machines… meaning you don’t like computers! OK, this is a bit of a stretch. But it is true that the definition of a Turing machine requires an arbitrary “jump” instruction that is able to change the instruction pointer to anywhere in the code. And this is exactly how branching conditionals are implemented at the CPU code! Check something, then jump somewhere else in the code!

read_file:
.LFB2:
	;; ...
	cmp	QWORD PTR -96[rbp], 0       ; [1]
	jne	.L6                         ; [2]
	call	__errno_location@PLT    ; [3]
	;; ...
.L6:
	mov	rdx, QWORD PTR -96[rbp]
	lea	rax, -80[rbp]
	;; ...

This is a small snippet from the assembled code of the read_file() subroutine at the current version of schain.c. Have a look at what’s going there: at [1] we’re comparing whether a memory location in the stack is zero or not. That’s what cmp tells the CPU to do: compare two things,1 but, unlike an if-clause like those in other languages, where you must state the consequence after the condition, in ASM you could just ignore the result and not do anything with it.

Yet we jump on [2]. jne .L6 means, “jump to label .L6 if the last comparison was not-equal.” So, if [1] resulted in that stack address not being zero, we jump to .L6 and keep running from there; otherwise, [3] is executed and the program runs onwards. You know, CPUs are dumb, so they let you jump wherever you want to… even illegal addresses… Well, OK, if you’re on a modern CPU running on any of the various Protected Modes (e.g. 64 bits Long Mode, as you’re probably running right now if you’re on an 64 bits x86 CPU…), the MMU will fire a segfault and your code will die on spot. But if you’re running on Real Mode, oh girl the mess you can get to.

So, jmp (and friends like jne above) is just a Goto… as unstructured as the bad, evil Gotos you’ve probably read about… The funny thing is that structured programming is implemented on top of unstructured programming so… there’s a story behind the bad reputation of Goto…

GOTO/GOSUB on BASIC

As far as I know, BASIC is the main culprit for everyone hating Gotos. GOTO on BASIC was as unstructured as it could get. And as I happen to have an MS-DOS 6.22 VM lying around, let’s fire up QuickBASIC 4.5.

QuickBASIC 4.5

OK, QuickBASIC 4.5 isn’t the purest breed of BASIC. QBASIC 1.1 wasn’t either. The story of these late MS-DOS BASIC implementations is that they came to be as a replacement of a very strange set of transition implementations that relied on the IBM PC’s BASIC ROM chip. Yes, the original IBM PC had BASIC installed on a ROM chip exactly like the previous generation of 8-bits microcomputers had… In general, the pre-PS/2 IBM PC was sort of a weird hybrid that previewed the 16 bits era, but had some legacy stuff that put it closer to the 8 bits micros… I think they got rid of the BASIC ROM somewhere in the early PS/2 line, but yes, in the early days, PCs booted into BASIC if no OS disk found!

So, IBM BASICA and later GW-BASIC, both actually licensed from Microsoft, for PC-DOS (later rebranded MS-DOS), relied on some features in the so-called Cassette BASIC that was installed on the ROM chip. Yup, this was an attempt on trying to prevent the proliferation of PC clones, the chip being proprietary… and was futile. I think GW-BASIC could do without those features, as it reimplemented them in software, but the end of the story is that QBASIC and the later QuickBASIC series were meant to create a BASIC interpreter fully implemented on software for the PS/2 line and onwards. Keep in mind that MS was starting to make deals with PC clones manufacturers, so they wanted to have something that didn’t rely on IBM’s specific custom ROMs.

Why am I telling you all of this? Because QBASIC/QuickBASIC were actually structured programming languages. They supported subroutines, functions, and were actually very, very nice languages in my opinion, nostalgia aside.2 But, in the truest ol' Bill Gates’s Microsoft ethos, they went for backwards compatibility so that they could attract as many users they could that were used to the old MS BASIC implementations that dominated the microcomputer scene.3 So much so, that the documentation has pages, and pages of materials dedicated to users of previous implementations of BASIC!

Old habits die hard. When people wanted to write some program to do something on their MS-DOS PC, they fired up the already included QBASIC (QuickBASIC was a separate purchase), wrote some quick code, and called it a day. And these people were probably used to the old unstructured hardware-based versions of BASIC. Not so many people used computers back then, but those who did were used to coding small stuff to get things done… And again, I mean, we’re all kinda lazy, aren’t we? If the implementation claims to be backwards compatible, why should I learn how to use SUB and FUNCTION if I can use plain ol' GOTO, right?

Well, that might be good strategy if it’s something you write in a rush… but if that code starts to grow and needs to be properly mantained… ohnoes.

So BASIC code became famous for being horrible, unstructured, etc. I guess this is also why Visual Basic, QBASIC/QuickBASIC’s immediate successor, tried so hard to write everything in a structured way. Branding matters, doesn’t it? You wouldn’t let your new RAD tool to suffer from the stereotypes of its old incarnations, would you?

There was GOSUB though. To explain what GOSUB is, it’s like a hybrid between a jump and declaring a subroutine… but without scope delimitation of any kind. The key feature of GOSUB is that it stored where you had jumped from and let you return to the jump point using RETURN. A bit like call/ret in x86 ASM, which jumps to the label you tell it, but stores the origin address so that you go back to where you were. As far as I remember from my teens,4 the QBASIC game development community usually favored using GOSUB over proper subroutines… I guess for compatibility issues? Well, my memory might be failing me on this… What I do remember is that GOTO was used less the bigger the codebase was… which makes sense.

But what about C?

Funny thing is that goto in C was already there from the beginning. And it was also available on other languages like Pascal, but let’s focus on C, shall we?

There’s goto. That seems like an obvious one. It’s a structured Goto, though, meaning that it will only allow you to jump to a label inside the same subroutine… which makes an awful lot of sense in the language that defines stack frames as part of its ABI. People are usually very, very wary of using it, so you don’t see it that much in the wild…

And a couple of very bad, somewhat memorable bugs where goto had been used don’t help in giving it better press, don’t you think?

Oh, but there are two other Goto statements everybody loves to use in C. Wait, what? Two other Gotos in C? Yes, and if you’re writing C, I bet you use them! Ready to get your mind blown? (Ari, cut the clickbait, will ya?)

break and continue! Those are just plain ol' Gotos, just limited in scope. Even the break you use to “close” a case statement is a Goto; without one you’re going to fall through the next case statement! Both break and continue statements make the execution jump to a specific point, namely the instruction immediate after the block or the one that starts it, respectively… but you can implement exactly the same behavior of both statements just by using goto.

By the way, Python makes a point of not supporting goto, but supports the break/continue pair and even supports exceptions, which are just a fancy computed (unstructured!) jump… You could totally re-create Goto on Python using exceptions; I’m pretty sure. Haven’t tried… Not interested… But I do see it possible…

There’s a counter-movement though. I’m not sure how recent, though? I know these guys are politically awful (and that’s an understatement), but in the suckless coding style there’s a recommendation to use goto to break from nested loops… Together with early returns, that’s quite a good advice to follow… as it makes your code jump directly to something useful as soon as you completed something, whatever it is. Kernel style also favors using goto, and Aiju is right… using goto won’t get you killed by raptors.

What’s for me in all of this?

It’s fairly obvious I’m in the Goto-crowd. I use it more or less in the suggested way: as an easy escape from nested loops or to avoid writing lots of conditionals when I know that the side-effects of an early condition allow me to skip all other tests right away. I think this snippet shows a good example of my typical use of goto:

static void
eval_math(const char *expr, Stack *stack)
{
	double dest, dx;
	char expr_cpy[SCALC_EXPR_SIZE];
	char *ptr, *endptr;
	const OpReg *op_ptr;

	/* We need to operate on a copy, as strtok is destructive. */
	strlcpy(expr_cpy, expr, SCALC_EXPR_SIZE);
	ptr = strtok(expr_cpy, " ");
	while (ptr != NULL) {
		dx = strtof(ptr, &endptr);
		if (endptr[0] == '\0')
			goto pushnum; /* If number, skip further parsing */

		if (mem_get(&dx, ptr[0]) == 0)
			goto pushnum;

		op_ptr = op(ptr);
		if (op_ptr->type == OP_NULL) {
			fprintf(stderr, "%s: undefined operation.\n", ptr);
			return;
		}

		if (apply_op(&dx, op_ptr, stack) < 0) {
			fprintf(stderr, "%s: %s\n", ptr, stack_errmsg());
			return;
		}

pushnum:
		if (stack_push(stack, dx) < 0) {
			fprintf(stderr, "%s: %s\n", expr, stack_errmsg());
			return;
		}
		ptr = strtok(NULL, " ");
	}

	if (stack_peek(&dest, *stack) < 0) {
		fprintf(stderr, "%s: %s\n", expr, stack_errmsg());
		return;
	}

	print_num(dest);
}

Without using Gotos, this would be a real challenge to get straight, to be honest… especially because the code labelled as pushnum must be run even when we’re not explicitly jumping to it. What would the alternative be? Writing pushnum as another subroutine, passing stuff by reference again? Yeah, possible… Worth it? I don’t think so.

goto exit;

One of the best lessons I was taught very early on when I was starting to hang around programming forums and IRC chat rooms was to avoid cargo cult programming. I think that’s an important lesson to take always into account…

Programming is an art. To me that means it’s all about having lots of tools at your disposal, millions of possibilities for you to try… and mostly deciding what’s going to fit your purpose… And that’s by nature something you do in a case-by-case fashion. I mean, no project is equal to another. You’re not the same coder you were some years ago… and you will change in a couple of years, as well…

“Hard rules” are useful for the beginner, but there’s always a moment in which you have to break them. I still remember when I started using Gotos again, now in C, after years and years avoiding them, believing they were intrinsically “evil.” That moment was somewhat special, because I knew I was growing as a coder, making the code a bit more my own.

I know we’re all very opinionated in the programming world… rightly so, because I think we’re all very passionate about technology. That’s a kind of diversity that I effing love and that FOSS makes way more explicit, because we’re all working in the open. Yes, there are some objective truths in programming,5 because this is a science in which you can measure things up, but the creative part of it opens code to a form of self-expression.

And self-expression comes from looking at the “hard rules” and saying “Hey, I know people won’t like this, but I can prove this is better than the usual way of doing this.” Those little experiments, those quirks that make your code yours, make programming move further and grow as a science and as an art.

So maybe you find Gotos distasteful… or maybe you’ve found a way to solve a problem with them. And of course I’m not talking just about Gotos… Gotos are just an example… Every language feature, every tool, subroutine, etc. can be reused in an unexpected way that is awesome and makes your code better. Experiment, have fun, learn the rules, and learn to break them.


  1. There are other ways to compare things on x86 Assembly. Whether you use a traditional or, test, or cmp depends a lot on what kind of flags you are going to check afterwards. ↩︎

  2. The QBASIC/QuickBASIC development documentation is within the best I have yet to see. The care for detail is incredible, with lots of examples, lots of hyperlinks to related entries… I wonder how many hours went in writing those manuals, but I guess quite a lot. Microsoft is evil, but they did know how to create awesome products in the 80s and 90s. ↩︎

  3. By the way, why wasn’t MS ever sued because of these predatory business practices? I mean, they even got Apple to use their BASIC ROMs! The only 8-bit platform that used a non-MS BASIC was Sinclair’s ZX line, as far as I know. ↩︎

  4. Bio note here before you ask me if I’m that old… which I’m not! I was born in 1988 and had my first contact with a computer at age 4… MS-DOS 5.x, I presume. Long story short: I got to know BASIC first from my granddad, who showed me his old ZX Spectrum 48K, and then from playing around with QBASIC on my family’s PC in the early 90s, which ran Windows 3.11. When we upgraded to Win9x I got my dad to purchase a copy of QuickBASIC 4.5, which actually ran quite well on Win95 and Win98. It was then when I started looking around game’s people were writing in QBASIC and posting them on their webpages… Wow, I do feel the nostagia… and I’m just a millennial girl! ↩︎

  5. Like C++ being the worst thing ever. ↩︎