用C或C ++编写好的getos的示例[关闭]


79

在这个线程中,我们看一下goto在C或C ++中良好使用的示例。灵感来自人们以为我在开玩笑而投票赞成的答案

摘要(标签从原始更改为意图更加清晰):

infinite_loop:

    // code goes here

goto infinite_loop;

为什么比其他更好:

  • 具体。 goto是导致无条件分支的语言构造。替代方案取决于使用支持条件分支的结构,并带有退化的始终为真的条件。
  • 标签记录了意图,没有额外的注释。
  • 读者不必扫描介入的代码以获取早期的breaks(尽管无原则的黑客仍然有可能continue用Early进行模拟 goto)。

规则:

  • 假装傻瓜没有赢。可以理解,以上内容不能在实际代码中使用,因为它违反了既定的习惯用法。
  • 假设我们都听说过“ Goto认为有害”,并且知道goto可以用来编写意大利面条式代码。
  • 如果您不同意一个示例,则仅凭技术优势对其进行批评(“因为人们不喜欢goto”不是技术原因)。

让我们看看是否可以像大人一样谈论这件事。

编辑

这个问题似乎已经解决了。它产生了一些高质量的答案。感谢所有人,尤其是那些认真对待我的小循环榜样的人。大多数怀疑论者担心缺乏障碍的范围。正如@quinmars在评论中指出的那样,您始终可以在循环主体周围放置括号。我顺便指出for(;;),并while(true)没有给你免费任括号(并忽略他们可能会导致错误伤脑筋)。无论如何,我不会在这个琐事上浪费您的脑力-我可以忍受无害和惯用的语言for(;;)while(true)(如果我想继续工作,也可以)。

考虑到其他答复,我看到许多人认为goto您总是必须用另一种方式重写。当然,您可以goto通过引入循环,额外的标志,嵌套的ifs或其他方法来避免使用a ,但是为什么不考虑goto也许这是最好的工具呢?换句话说,人们准备忍受多少丑陋,以避免将内置语言功能用于其预期目的?我的观点是,即使添加一个标志也付出了很高的代价。我喜欢用变量来表示问题或解决方案领域中的事物。goto仅仅避免'不会削减它。

我将接受第一个答案,该答案给出了用于分支到清除块的C模式。IMO,这是goto所有已发布答案中最有力的理由,当然,如果您以仇恨者必须避免的扭曲来衡量它,那么当然。


11
我不明白为什么在项目的包含文件顶部,傻瓜们不仅仅要求“ #define goto report_to_your_supervisor_for_re_education_through_labour”。如果总是错误,请使其成为不可能。否则,有时候是对的……
史蒂夫·杰索普

14
“不能在真实代码中使用”?每当它是完成工作的最佳工具时,我都会在“真实”代码中使用它。“既定习语”是“盲目教条主义”的很好委婉说法。
Dan Moulding 2010年

7
我同意“ goto”很有用(下面有很多很好的例子),但是我不同意您的具体例子。一行显示“ goto infinite_loop”的声音听起来像是“转到我们将永远开始循环的代码部分”,如“ initialize(); set_things_up(); goto infinite_loop;”中所示。当您真正要传达的意思是“开始我们已经进入的循环的下一个迭代”时,这是完全不同的。如果您要尝试循环,并且您的语言具有专门为循环而设计的结构,请使用这些结构以使其清晰。while(true){foo()}非常明确。
乔什(Josh)2010年

6
一个主要的缺点你的例子是,它不是明显ohter代码中的位置可以决定跳到这个标签。“正常”无限循环(for (;;))没有令人惊讶的入口点。
jalf

3
@György:是的,但这不是切入点。
jalf

Answers:


87

这是我听说过人们使用的一种技巧。我从来没有在野外看到它。它仅适用于C,因为C ++具有RAII来更习惯地执行此操作。

void foo()
{
    if (!doA())
        goto exit;
    if (!doB())
        goto cleanupA;
    if (!doC())
        goto cleanupB;

    /* everything has succeeded */
    return;

cleanupB:
    undoB();
cleanupA:
    undoA();
exit:
    return;
}

2
这怎么了 (除了注释不理解换行符的事实之外)void foo(){if(doA()){if(doB()){if(doC()){/ *一切成功* /返回;} undoB(); } undoA(); } return; }
Artelius

6
多余的块会导致不必要的缩进,难以理解。如果其中一个条件在循环内,它也不起作用。
亚当·罗森菲尔德

26
您可以在大多数底层Unix程序(例如linux内核)中看到很多这类代码。在C语言中,这是错误恢复恕我直言的最佳习惯。
David Cournapeau

8
正如cournape所述,Linux内核一直都在使用这种样式。
CesarB

8
不仅Linux内核可以查看Microsoft的Windows驱动程序样本,而且您会发现相同的模式。通常,这是一种处理异常的C方法,非常有用:)。我通常只喜欢1个标签,在少数情况下更喜欢2。在99%的情况下,可以避免使用3。
伊利亚

85

C语言中对GOTO的经典需求如下

for ...
  for ...
    if(breakout_condition) 
      goto final;

final:

没有goto,没有直接的方法可以摆脱嵌套循环。


49
通常,当这种需求出现时,我可以使用退货代替。但是您需要编写一些小的函数才能使其工作。
大流士培根

7
我绝对同意Darius-将其重构为函数然后返回!
metao

9
@metao:Bjarne Stroustrup表示不同意。在他的C ++编程语言书中,这正是goto的“良好用法”示例。
paercebal

19
@Adam Rosenfield:Dijkstra强调了goto的全部问题,就是结构化编程提供了更易理解的构造。为了避免goto使得代码难以阅读证明了作者无法理解该论文……
Steve Jessop

5
@史蒂夫·杰索普(Steve Jessop):不仅人们误解了这篇论文,而且大多数“不去做”的信徒们都不理解这篇论文的历史背景。
我的正确观点

32

这是我的非傻子示例(来自Stevens APITUE),用于Unix系统调用,该调用可能会被信号中断。

restart:
    if (system_call() == -1) {
        if (errno == EINTR) goto restart;

        // handle real errors
    }

替代方案是简并循环。该版本的读法类似于英语“如果系统调用被信号中断,请重新启动它”。


3
例如,这种方式用于linux调度程序具有这样的goto,但我会说很少有向后goto可以接受的情况,通常应该避免。
伊利亚

8
@Amarghosh,continue只是goto一个面具。
jball 2010年

2
@jball ...嗯...为了辩论,是的;您可以继续使用goto和spaghetti代码制作出具有良好可读性的代码。最终,这取决于编写代码的人。关键是,与goto相比,goto更容易迷失。而且,新手通常会使用遇到的每个问题的第一把锤子。
Amarghosh 2010年

2
@Amarghosh。您的“改进”代码甚至不等同于原始代码-成功案例中它将永远循环。
fizzer 2010年

3
@fizzer oopsie ..它需要两个else breaks(每个ifs一个)以使其等效。.我能说什么...goto或不,您的代码只和您一样好:(
Amarghosh 2010年

14

如果Duff的设备不需要goto,那么您也不需要!;)

void dsend(int count) {
    int n;
    if (!count) return;
    n = (count + 7) / 8;
    switch (count % 8) {
      case 0: do { puts("case 0");
      case 7:      puts("case 7");
      case 6:      puts("case 6");
      case 5:      puts("case 5");
      case 4:      puts("case 4");
      case 3:      puts("case 3");
      case 2:      puts("case 2");
      case 1:      puts("case 1");
                 } while (--n > 0);
    }
}

上面维基百科条目中的代码。


2
Cmon伙计们,如果您要投票,请简短说明为什么会很棒。:)
米奇小麦

10
这是一个逻辑谬论的例子,也被称为“呼吁权威”。在Wikipedia上查看。
Marcus Griep

11
幽默在这里常常不被人欣赏……
史蒂芬·A·洛

8
达夫的设备能酿啤酒吗?
史蒂文·劳

6
这个人一次可以做8个;-)
Jasper Bekkers,

14

Knuth撰写了一篇论文“使用GOTO语句进行结构化编程”,例如,您可以从此处获得。您会在那里找到许多示例。


本文看起来很深。我会读的。谢谢
fizzer

2
那张纸太过时了,甚至都不好笑。克努斯的假设在那里不再成立。
Konrad Rudolph,2009年

3
有哪些假设?对于过程语言而言,他的示例与当时的C一样真实(他为C提供了伪代码)。
zvrba

8
即时通讯很失望,您没有写::您可以转到“这里”得到它,对我来说,双关语会让人难以忍受!
RhysW

12

很常见。

do_stuff(thingy) {
    lock(thingy);

    foo;
    if (foo failed) {
        status = -EFOO;
        goto OUT;
    }

    bar;
    if (bar failed) {
        status = -EBAR;
        goto OUT;
    }

    do_stuff_to(thingy);

OUT:
    unlock(thingy);
    return status;
}

我曾经用过的唯一情况goto是向前跳,通常跳出区块,从不跳入区块。这样可以避免滥用do{}while(0)和其他会增加嵌套的结构,同时仍保持可读的结构化代码。


5
我认为这是错误处理的典型C方法。我看不到它被很好地替换了,以其他方式更易于阅读问候
Friedrich

为什么不... do_stuff(thingy) { RAIILockerObject(thingy); ..... /* -->*/ } /* <---*/ :)
ПетърПетров

1
@ПетърПетров这是C ++中的一种模式,在C中没有类似物
。– ephemient

或者使用比C ++更结构化的语言,try { lock();..code.. } finally { unlock(); }但是C是没有等效的。
Blindy 2014年

12

一般而言,我对goto毫无抵触,但我可以想到几个您不想将它们用于循环的原因:

  • 它没有限制范围,因此您稍后使用在其中使用的任何临时变量都不会被释放。
  • 它不限制范围,因此可能导致错误。
  • 它不会限制作用域,因此以后您将无法在以后的代码中在相同作用域中重复使用相同的变量名。
  • 它没有限制范围,因此您有机会跳过变量声明。
  • 人们不习惯它,它将使您的代码更难阅读。
  • 这种类型的嵌套循环可能导致意大利面条代码,法线循环不会导致意大利面条代码。

1
是inf_loop:{/ *循环主体* /}转到inf_loop; 更好?:)
quinmars

2
对于此示例,即使没有括号,第1-4点也是可疑的。1这是一个无限循环。2太含糊,无法解决。3试图在同一个作用域中隐藏相同名称的变量是不明智的做法。4.向后的goto不能跳过声明。
嘶嘶声

像所有无限循环一样,1-4通常在中间有某种中断条件。因此它们是适用的。笨拙的goto不能跳过声明...并非所有的东西都向后退...
Brian R. Bondy

7

使用goto的一个好地方是可以在多个点中止的过程中,每个过程都需要进行不同级别的清理。Gotophobes总是可以用结构化代码和一系列测试来代替gotos,但是我认为这更直接,因为它消除了过多的缩进:

如果(!openDataFile())
  退出

如果(!getDataFromFile())
  转到closeFileAndQuit;

如果(!allocateSomeResources)
  转到freeResourcesAndQuit;

//在这里做更多的工作...

freeResourcesAndQuit:
   //免费资源
closeFileAndQuit:
   //关闭文件
放弃:
   // 放弃!

我会用一组函数替换此:freeResourcesCloseFileQuitcloseFileQuit,和quit
RCIX

4
如果创建了这3个额外的函数,则需要将指针/句柄传递给要释放/关闭的资源和文件。您可能会让freeResourcesCloseFileQuit()调用closeFileQuit(),而后者又将调用quit()。现在,您需要维护4个紧密耦合的函数,其中最多3个可能会被调用一次:从上面的单个函数开始。如果您坚持要避免goto,则IMO嵌套的if()块的开销较小,并且更易于阅读和维护。您可以通过3种额外功能获得什么?
亚当·利斯

7

@ fizzer.myopenid.com:您发布的代码段等效于以下内容:

    while (system_call() == -1)
    {
        if (errno != EINTR)
        {
            // handle real errors

            break;
        }
    }

我绝对喜欢这种形式。


1
OK,我知道break语句只是转到一个更容易接受的形式..
米奇小麦

10
在我看来,这令人困惑。这是您不希望在正常执行路径中进入的循环,因此逻辑似乎倒退了。不过,意见很重要。
嘶嘶声

1
向后?它开始,继续,直到失败。我认为这种形式更明显地表明了其意图。
米奇小麦

8
我在这里同意fizzer的观点,即goto对条件的值提供了更清晰的期望。
Marcus Griep

4
用哪种方式比起fizzer的方式更容易阅读。
erikkallen

6

尽管随着时间的流逝我已经讨厌这种模式,但它已成为COM编程的主要内容。

#define IfFailGo(x) {hr = (x); if (FAILED(hr)) goto Error}
...
HRESULT SomeMethod(IFoo* pFoo) {
  HRESULT hr = S_OK;
  IfFailGo( pFoo->PerformAction() );
  IfFailGo( pFoo->SomeOtherAction() );
Error:
  return hr;
}


1

我已经看到goto正确使用了,但是情况通常很难看。只有当其使用goto本身比原来的使用要差得多时。@Johnathon Holland的问题是您的版本不太清楚。人们似乎害怕局部变量:

void foo()
{
    bool doAsuccess = doA();
    bool doBsuccess = doAsuccess && doB();
    bool doCsuccess = doBsuccess && doC();

    if (!doCsuccess)
    {
        if (doBsuccess)
            undoB();
        if (doAsuccess)
            undoA();
    }
}

我更喜欢这样的循环,但有些人更喜欢while(true)

for (;;)
{
    //code goes here
}

2
永远不要将布尔值与true或false进行比较。已经是布尔值了;只需使用“ doBsuccess = doAsuccess && doB();” 和“ if(!doCsuccess){}”代替。它更简单,可以保护您免受编写“ #define true 0”之类的聪明程序员的侵害,因为,因为他们可以。它还可以保护您免受将int和bool混合,然后假定任何非零值为true的程序员的侵害。
亚当·利斯

谢谢,点== true已删除。无论如何,这更接近我的真实风格。:)
Charles Beattie

0

我对此的抱怨是,您失去了块作用域;如果循环中断,则在goto之间声明的任何局部变量将保持有效。(也许您假设循环将永远运行;不过,我认为那不是原始问题作者所要问的。)

范围问题更多地是C ++的问题,因为某些对象可能取决于在适当时间调用它们的dtor。

对我来说,使用goto的最佳理由是在多步骤初始化过程中,至关重要的是,如果失败了,则必须退出所有init,例如:

if(!foo_init())
  goto bye;

if(!bar_init())
  goto foo_bye;

if(!xyzzy_init())
  goto bar_bye;

return TRUE;

bar_bye:
 bar_terminate();

foo_bye:
  foo_terminate();

bye:
  return FALSE;

0

我不使用goto的本人,但是我曾经与一个人合作过,可以在特定情况下使用它们。如果我没记错的话,他的理由是围绕绩效问题-他也有关于如何的具体规则。始终具有相同的功能,并且标签始终位于goto语句之下。


4
附加数据:请注意,您不能使用goto超出函数范围,并且在C ++中,禁止通过goto绕过对象构造
paercebal

0
#include <stdio.h>
#include <string.h>

int main()
{
    char name[64];
    char url[80]; /*The final url name with http://www..com*/
    char *pName;
    int x;

    pName = name;

    INPUT:
    printf("\nWrite the name of a web page (Without www, http, .com) ");
    gets(name);

    for(x=0;x<=(strlen(name));x++)
        if(*(pName+0) == '\0' || *(pName+x) == ' ')
        {
            printf("Name blank or with spaces!");
            getch();
            system("cls");
            goto INPUT;
        }

    strcpy(url,"http://www.");
    strcat(url,name);
    strcat(url,".com");

    printf("%s",url);
    return(0);
}

-1

@格雷格:

为什么不这样做呢?

void foo()
{
    if (doA())
    {    
        if (doB())
        {
          if (!doC())
          {
             UndoA();
             UndoB();
          }
        }
        else
        {
          UndoA();
        }
    }
    return;
}

13
它增加了不必要的缩进级别,如果您有很多可能的故障模式,则缩进可能会非常麻烦。
亚当·罗森菲尔德

6
我会在goto上缩进。
FlySwat

13
而且,这需要更多的代码行,在一种情况下具有冗余的函数调用,并且很难正确地添加国防部。
帕特里克

6
我同意Patrick的观点-我已经见过这种习语在几个条件下使用,嵌套到大约8个层次的深度。真讨厌 而且非常容易出错,尤其是在尝试添加新内容时。
Michael Burr

11
这段代码只是大声疾呼:“下几个星期我会发现一个错误”,有人会忘记撤消某些操作。您需要在同一位置进行所有撤消操作。就像本地OO语言中的“最终”一样。
伊利亚
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.