C:嵌套的 If 或 Goto

发布于 2024-10-01 14:49:47 字数 3302 浏览 1 评论 0原文

管理 C 程序资源的最佳方式是什么?我应该使用嵌套的 if 结构还是应该使用 goto 语句?

我知道关于 goto 语句有很多禁忌。不过,我认为本地资源清理是合理的。我提供了两个样品。一种比较嵌套的 if 结构,另一种使用 goto 语句。我个人发现 goto 语句使代码更易于阅读。对于那些可能认为嵌套 if 提示更好的结构的人来说,想象一下数据类型是否不是 char*,比如 Windows 句柄。我觉得嵌套的if结构会因一系列CreateFile 函数或任何其他需要大量参数的函数。

这篇文章演示了本地 goto 语句为 C 代码创建 RAII。代码简洁且易于理解。将其想象为一系列嵌套 if 语句。

我知道 goto 在许多其他语言中是禁忌,因为它们存在其他控制机制,如 try/catch 等,但是,在 C 中似乎是合适的。

#include <stdlib.h>

#define STRING_MAX 10

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

    if ( !(string1 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto gotoExample_string1;
    if ( !(string2 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto gotoExample_string2;
    if ( !(string3 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto gotoExample_string3;
    if ( !(string4 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto gotoExample_string4;
    if ( !(string5 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto gotoExample_string5;

    //important code goes here

gotoExample_string5:
    free(string4);
gotoExample_string4:
    free(string3);
gotoExample_string3:
    free(string2);
gotoExample_string2:
    free(string1);
gotoExample_string1:
}

void nestedIfExample()
{
    char *string1, *string2, *string3, *string4, *string5;

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


int main(int argc, char* argv[])
{
    nestedIfExample();
    gotoExample();
    return 0;
}

我还想引用 Linus Torvalds 关于 Linux Kernel 内的 goto 语句一个>。

有时结构不好,并且 妨碍,并使用“goto” 更加清晰。

例如,这是很常见的 有不嵌套的条件。

在这种情况下你有两个 可能性

  • 使用 goto,并且很高兴,因为它不强制嵌套

    这使得代码更具可读性, 因为代码只是做了什么 算法说它应该这样做。

  • 复制代码,并以嵌套形式重写,以便可以
    使用结构化跳转。

    这通常会使代码变得更少 可读,难以维护,并且 更大。

Pascal 语言就是一个典型的例子 的后一个问题。因为它 没有“break”声明, (传统)Pascal 中的循环最终结束 通常看起来完全是一坨狗屎,因为 你必须添加完全任意的 逻辑上说“我现在完成了”。

goto 是否可以用于资源管理?我应该使用嵌套if语句还是有更好的方法?

更新: C 语言中良好的 Goto 示例< /a>

What is the best way to manage resources for a C program. Should I use a nested if structure or should I use goto statements?

I am aware there is a lot of taboo about goto statements. However, I think it is justified for local resource clean up. I have supplied two samples. One compares a nested if structure and another uses goto statements. I personally find the goto statements make the code easier to read. For those who might argue that the nested if prompt better structure, imagine if the datatype was something other than a char*, like a Windows handle. I feel that the nested if structure would get out of hand with a series of CreateFile functions or any other function that takes large quantities of parameters.

This article demonstrates that local goto statements create RAII for C code. The code is neat an easy to follow. Imagine that as a series of nested if statements.

I understand that goto is taboo in many other languages because their exists other control mechanisms like try/catch etc, however, in C it seems appropriate.

#include <stdlib.h>

#define STRING_MAX 10

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

    if ( !(string1 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto gotoExample_string1;
    if ( !(string2 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto gotoExample_string2;
    if ( !(string3 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto gotoExample_string3;
    if ( !(string4 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto gotoExample_string4;
    if ( !(string5 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto gotoExample_string5;

    //important code goes here

gotoExample_string5:
    free(string4);
gotoExample_string4:
    free(string3);
gotoExample_string3:
    free(string2);
gotoExample_string2:
    free(string1);
gotoExample_string1:
}

void nestedIfExample()
{
    char *string1, *string2, *string3, *string4, *string5;

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


int main(int argc, char* argv[])
{
    nestedIfExample();
    gotoExample();
    return 0;
}

I would also like to quote Linus Torvalds on goto statements inside the Linux Kernel.

And sometimes structure is bad, and
gets into the way, and using a "goto"
is just much clearer.

For example, it is quite common to
have conditionals THAT DO NOT NEST.

In which case you have two
possibilities

  • use goto, and be happy, since it doesn't enforce nesting

    This makes the code more readable,
    since the code just does what the
    algorithm says it should do.

  • duplicate the code, and rewrite it in a nesting form so that you can
    use the structured jumps.

    This often makes the code much LESS
    readable, harder to maintain, and
    bigger.

The Pascal language is a prime example
of the latter problem. Because it
doesn't have a "break" statement,
loops in (traditional) Pascal end up
often looking like total shit, because
you have to add totally arbitrary
logic to say "I'm done now".

Is goto acceptable for resource management? Should I use nested if statements or is there a better way?

Update: Examples of Good Gotos In C

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

百合的盛世恋 2024-10-08 14:49:48

我个人过去曾以这种方式使用 goto。人们讨厌它,因为它让他们想起他们曾经编写/维护的意大利面条代码,或者因为编写/维护此类代码的人打破了他们认为 goto 是邪恶的概念。

当然,你可能可以写出一些不错的东西,而不需要 goto。但在这种情况下它不会造成任何伤害。

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.

给我一枪 2024-10-08 14:49:48

从您的两个选择中,goto 自然更好更好。但还有第三种更好的选择:使用递归!

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

转身泪倾城 2024-10-08 14:49:48

我的代码结构与任何一种都不同。除非我有一些突出的理由要这样做,否则我可能会编写如下代码:

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]);

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]);
筱果果 2024-10-08 14:49:48

您链接到的文章中的示例与您发布的代码之间的一个很大的区别是您的 goto 标签是 _ ,它们的 goto 标签是 cleanup_< ;thing_to_cleanup>

接下来您将使用 goto line_1324,并且代码将被编辑,因此 line_1234 标签位于第 47823 行...

像示例一样使用它,并且要非常小心编写要阅读的代码。

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.

断念 2024-10-08 14:49:48

如果您知道自己在做什么,并且生成的代码看起来更干净、更具可读性(我敢打赌确实如此),那么使用 goto 绝对没有问题。特别是您展示的“优雅初始化失败恢复”示例被广泛使用。

顺便说一句,当编写初始化 100 个东西的结构化代码时,您需要 100 级缩进……这实在是丑陋。

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.

深府石板幽径 2024-10-08 14:49:48

在 C 中,goto 通常是近似清理代码的唯一方法,如 C++ 析构函数或 Java finally 子句。因为它确实是实现此目的的最佳工具,所以我建议使用它。是的,它很容易被滥用,但很多编程结构也很容易被滥用。例如,大多数 Java 程序员会毫不犹豫地抛出异常,但如果异常用于错误报告以外的其他用途(例如流程控制),则异常也很容易被滥用。但是,如果您显式使用 goto 是为了避免清理代码重复,那么可以肯定地说您可能没有滥用它。

例如,您可以在 Linux 内核中找到 goto 的许多完全合理的用法。

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.

圈圈圆圆圈圈 2024-10-08 14:49:48

对我来说,我更喜欢这种 go​​to 错误处理风格。 Nick D 的代码片段更进一步,它使用一个通用的 goto 标签来进行错误处理。

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);

}

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);

}
汐鸠 2024-10-08 14:49:47

毫无疑问,Dijkstra 是编程界的一位令人敬畏的人物。他的
Goto 被认为有害论文
太夸张了。是的,GoTo 可能会被滥用并且可能有害,但许多人认为彻底禁止 GoTo 是
没有保证。高德纳 (Knuth) 对迪杰斯特拉 (Dijkstra) 提出了非常有理有据的反驳:
使用 GO TO 进行结构化编程

阅读 Knuth 的论文,您将发现你的 GoTo 模式是很好的用途之一
对于转到。

顺便说一句,迪杰斯特拉在许多其他事情上也很容易被引用。怎么样:

  • 面向对象编程是一个非常糟糕的想法,它只能起源于加利福尼亚州。

Dijkstra 是一位伟大的数学家,对计算机科学做出了巨大贡献。然而,我不
99.99% 的人认为他必须处理日常事务,或者对这些事务感兴趣
我们的程序可以。

仅在有理由和结构的情况下使用 GoTo。很少使用它们。但一定要使用它们。

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.

一袭水袖舞倾城 2024-10-08 14:49:47

如果使用goto可以避免编写复杂的代码,那么就使用goto

您的示例也可以这样编写(没有 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);
}

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);
}
最单纯的乌龟 2024-10-08 14:49:47

使用 goto 进行清理的优点是不易出错。必须释放在每个返回点分配的所有资源可能会导致有一天有人在进行维护工作时错过一些清理工作。

也就是说,我将引用 Knuth 的“使用 goto 语句进行结构化编程”:

我主张在某些情况下消除 go to,并在其他情况下引入它们。

以及 Knuth 在同一篇论文中引用 Dijkstra 的话:

“请不要陷入相信我对 [go to 语句] 非常教条的陷阱。我有一种不舒服的感觉,其他人正在从中建立一种宗教,就好像编程的概念问题可以可以通过一个技巧、通过一种简单的编码规则来解决!” [29].

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].

池予 2024-10-08 14:49:47

总是在每个程序中使用一个 goto 来惹恼纯粹主义者,


这就是我的哲学。

说真的,在某些情况下,goto 是合理的,特别是如果它只是执行一些明显的操作,例如跳转到函数底部的公共返回代码。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文