C: Nested Ifs or Gotos

拜拜、爱过 提交于 2019-12-03 12:23:24

Cleanup using goto has the advantage that it's less error-prone. Having to free each and every resource allocated on each and every return point can lead to someone someday missing some cleanup when doing maintenance work.

That said, I'll quote Knuth's "Structured Programming with goto Statements":

I argue for the elimination of go to's in certain cases, and for their introduction in others.

and Knuth's quote of Dijkstra in that same paper:

"Please don't fall into the trap of believing that I am terribly dogmatical about [the go to statement]. I have the uncomfortable feeling that others are making a religion out of it, as if the conceptual problems of programming could be solved by a single trick, by a simple form of coding discipline!" [29].

No doubt about it Dijkstra was a formidable personality in the programming world. His Goto Considered Harmful paper was way overblown. Yes GoTo may be used indiscriminately and can be harmfull but many think an outright ban on GoTo is not warranted. Knuth provided a very well reasoned rebuttal to Dijkstra in: Structured Programming with GO TOs

Read Knuth's paper, you will find that your GoTo pattern is one of the good uses for GoTo.

BTW, Dijkstra is very quotable for a number of other things too. How about:

  • Object-oriented programming is an exceptionally bad idea which could only have originated in California.

Dijkstra was a great mathematician and made huge contributions to computer science. However, I don't think he had to deal with, or was interested in, the day to day type stuff that 99.99 percent of our programs do.

Use GoTo only with reason and structure. Use them rarely. But do use them.

If by using goto you can avoid writing complex code, then use goto.

Your example could also be written like this (no gotos):

void anotherExample()
{
    char *string1, *string2, *string3, *string4, *string5;
    string1 = string2 = string3 = string4 = string5 = 0;
    if ((string1 = (char*) calloc(STRING_MAX, sizeof(char)))
     && (string2 = (char*) calloc(STRING_MAX, sizeof(char)))
     && (string3 = (char*) calloc(STRING_MAX, sizeof(char)))
     && (string4 = (char*) calloc(STRING_MAX, sizeof(char)))
     && (string5 = (char*) calloc(STRING_MAX, sizeof(char))))
    {
       //important code here
    }

  free(string1);
  free(string2);
  free(string3);
  free(string4);
  free(string5);
}

Always use one goto in each program to annoy the purists


That's my philosophy.

Seriously, on some occasions a goto is reasonable, especially if it just does something obvious like jump to common return code at the bottom of a function.

Personally I have used goto in this manner in the past. People hate it because it reminds them of the spaghetti code they used to write/maintain, or because someone who wrote/maintaned such code beat the concept that gotos are evil into them.

You probably could write something decent without goto, sure. But it's not going to do any harm in this kind of circumstance.

From two of your alternatives, goto is naturally better and nicer. But there's a third and better alternative: Use recursion!

I'd structure the code differently than either one. Unless I had some outstanding reason to do otherwise, I'd probably write the code something like this:

char *strings[5] = {NULL};

int all_good = 1;

for (i=0; i<5 && all_good; i++) {
    strings[i] = malloc(STRING_MAX);
    all_good &= strings[i] != NULL;
}

if (all_good)
    important_code();

for (int i=0; i<5; i++)
    free(strings[i]);

One very big difference between the example in the article you link to and the code you're posting is that your gotos labels are <functionName>_<number> and their goto labels are cleanup_<thing_to_cleanup>.

You're be using goto line_1324 next, and the code will get edited so the line_1234 label is on line 47823 ...

Use it like the example, and be very careful to write code to be read.

If you know what you're doing, and the resulting code looks cleaner and more readable (I bet it does), then there's absolutely no problem with using goto's. Especially the 'graceful initialization failure recovery' example you showed is widely used.

BTW, when writing structured code that initializes 100 things, you'd need 100 levels of indentation...which is plain ugly.

In C, goto is often the only way to approximate cleanup code like C++ destructors or Java finally clauses. Since it's really the best tool you have for that purpose, I say use it. Yeah, it's easy to abuse, but so are a lot of programming constructs. For example, most Java programmers wouldn't hesitate to throw exceptions, but exceptions are also easy to abuse if they're used for something other than error reporting, such as flow control. But if you're using goto explicitly for the purpose of avoiding cleanup-code duplication, then it's safe to say you're probably not abusing it.

You can find many perfectly reasonable uses of goto in the Linux kernel, for example.

For me, I prefer this style of goto error handling. Taking Nick D's snippet one step further, it uses one general goto label for error handling.

void gotoExample()
{
    char *string1, *string2, *string3, *string4, *string5;
    string1 = string2 = string3 = string4 = string5 = NULL;

    if ( !(string1 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto HANDLE_ERROR;
    if ( !(string2 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto HANDLE_ERROR;
    if ( !(string3 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto HANDLE_ERROR;
    if ( !(string4 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto HANDLE_ERROR;
    if ( !(string5 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto HANDLE_ERROR;

    //important code goes here


HANDLE_ERROR:
  if (string5)
    free(string5);

  if (string4)
    free(string4);

  if (string3)
    free(string3);

  if (string2)
    free(string2);

  if (string1)
    free(string1);

}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!