使用goto语句会导致内存泄漏吗?

14

我有一个程序,其中需要跳出一大堆嵌套的for循环。到目前为止,大多数人告诉我在代码中使用丑陋的goto命令来实现。

那么,如果我在循环内部创建了许多本地堆栈(我认为这就是它们的名称,如果不是,我指的是只使用常规变量而不使用new命令的变量),并且我的程序触发了那个if语句以触发goto命令,是否会因为程序未正确清理本地变量而导致内存泄漏?


8
GOTO有什么问题? - Billy ONeal
12
是的,仅前进的goto跳转并不邪恶。我比每次循环都检查变量更喜欢它们。它们还非常适用于C语言中错误处理函数退出。 - Zan Lynx
2
如果您正在响应诸如除零之类的情况,则可能希望考虑抛出异常。 - D.Shawley
4
也许我在评论中有点严厉了。 我承认可能有一些领域 goto 并不会(太)有害。但我想引用这个答案中的第一个评论:“+1:如果需要考虑使用 goto,则它足够复杂,可以封装在函数中避免使用 goto。”:https://dev59.com/JXNA5IYBdhLWcg3wSrua#1024395. - R. Martinho Fernandes
2
return 看作是一个结构化的 goto。如果一个 return 会泄漏,那么一个 goto 也会泄漏。 (而且,当你在这方面时,考虑使用结构化方法代替意大利面条式方法,将代码放入自己的函数中并使用 return 跳出。Martinho 引用的经验法则非常好。) - sbi
显示剩余8条评论
10个回答

21

不会引起内存泄漏。使用goto并非“不正确退出循环”,仅是从代码结构的角度来看通常不建议使用

除此之外,当您离开循环时,局部变量将超出作用域并在过程中弹出堆栈(即被清理)。


为了提前退出循环,你可以采取以下方法:(a)使用带有标签的goto语句。(b)在循环之前声明一个额外的循环条件对象,并在每个循环的条件语句中进行检查。(c)类似于'b',但如果需要,检查条件对象的状态"break"。最后(d)将函数分成两部分,并使用return跳出循环。在我看来,只有其中一种方法能清楚地突显代码作者的意图。另一个好处是,goto使得搜索这些罕见情况变得非常容易,因此它们可以轻松地进行同行评审。 - Richard Corden

6

栈变量(自动变量,不是自动机器人)不像通过new()或malloc()分配的变量那样“泄漏”。

至于goto语句的“丑陋”,那只是教条主义。阅读 Knuth,他和 Dijkstra 一样聪明。http://pplab.snu.ac.kr/courses/adv_pl05/papers/p261-knuth.pdf避免基于意大利面的编程,但仔细使用不会退化成意大利面条。

Dijkstra 不喜欢它们,因为大多数可以使用其他结构化编程技术完成的goto操作可以使用更少的代码完成,从而使其他结构化编程方法更少出错。

要理解的是,goto不应该是您的第一个解决方案,也不要费力地使用它们,但如果有意义,请不要屈服于教条的暴民。break语句只是一个伪装成goto的命令,旨在处理严格遵守“不得使用goto”的规则不合理的情况。


1
Dijkstra 不喜欢它们,因为每个人习惯性地使用它们,这证明了它们是结构化编程采用的障碍。break 不再是“goto 的伪装”,就像 else 或 while 一样不是“goto 的伪装”。它们都是结构化编程结构,导致非顺序控制转移。 - Steve Jessop
4
“break”打破了Dijkstra非常喜欢的“单一出口”标准。它会将您从一个良好结构化的构造(如循环),该构造具有单个入口和单个出口中间退出。使用break必须与条件评估一起使用才能实用。如果<condition> goto是Dijkstra想要替换为结构化编程的语句类型。如果<condition> break是相同的结构,唯一的区别是goto被break取代。对于这些情况,“break”仅被认为比“goto”略好,因为您在可以跳转到的位置上受到限制。 - NoMoreZealots
Break比goto要好得多,原因在于只有一个地方可以使用break。这种限制正是结构化编程作为一种创新的区别所在。"伪装的goto"不过是麦卡锡主义者的诽谤——任何人都可以找到绝对任何流程控制和"goto"之间的某些相似之处。但这与所讨论的技术是否提供了帮助代码理解的结构无关。使用break来终止循环正是做到了这一点。 - Steve Jessop
在循环结构内部,当嵌套逻辑中使用breaks或gotos的数量增加时,分析性地确定软件退出状态变得棘手且成正比。然而,由于breaks“受到”循环的限制,因此它们相对更安全。Dijkstra提倡在编写每个程序时与之同时生成数学证明,这是他哲学的核心。Break的分析复杂度不符合他的数学优雅理念。再次强调,Dijkstra非常注重学术,更看重正确性而非生产力和效率。 - NoMoreZealots
提供的 PDF 文件已不再可用。 - Connor Clark
显示剩余4条评论

2
不行。你只能泄露动态分配的内存。

1
你可以通过其他方式泄漏内存。例如,在线程堆栈上分配几十兆字节,然后休眠一段时间 :) - bdonlan
+1,hobodave。您,先生,是今天第一个让我笑出声的人。 - jkeys
2
Faken并没有询问堆栈变量本身是否会泄漏,他询问的是由于堆栈变量未被清除而导致的任何泄漏情况。如果他使用了longjmp而不是goto,并且他的变量之一是向量,则会发生泄漏(隐藏在本地对象后面的动态分配内存)。在这种情况下,答案可以是“是的。您只能泄漏动态分配的内存”。因此,我认为解释一下Yes/No会很有帮助;-) - Steve Jessop

2

在进入函数时,栈变量被定义(和分配),一旦离开函数,它们就会被隐式消除(因为整个调用堆栈记录已经弹出)。无论在函数内部如何反复跳动,都不可能对一直分配的内存造成任何破坏。无论你通过代码走哪条执行路径,当控制返回到调用函数时,堆栈记录将弹出,内存将被释放。


是的,但如果我没有使用函数会发生什么呢?如果我在主程序中使用goto一次跳出多个循环,这是否也适用? - Faken
所有东西都在一个函数里面,即使那个函数是 main()。当程序启动时,main() 的堆栈记录被分配,并在程序结束时被丢弃 -- 这和任何其他函数的规则相同。 - VoteyDisciple
在C++和C中,您的代码始终在一个函数或另一个函数中运行。当然,那个函数可能是main()。 - Bill Forster
4
不正确:变量在整个“作用域”期间被推入和弹出堆栈。请看这段代码片段:http://codepad.org/oNcMPftp - xtofl

1

Goto并不总是不好的,但在您的情况下,您可能不应该使用goto。

请参阅goto的良好使用示例此处此处

如果您跳转到超出范围的标签,则堆栈上的对象将被释放。

例如:

#include <iostream>
using namespace std;

class A
{
public:
  ~A() 
  {
     cout<<"A destructor"<<endl;
  }
};



int main(int argc, char**argv)
{
  {
    A a;
    cout<<"Inside scope"<<endl;
    goto l;
    cout<<"After l goto"<<endl;
  }

  cout<<"Outside of scope before l label"<<endl;

l:
  cout<<"After l label"<<endl;
  return 0;
}

这将打印出:

作用域内
一个析构函数
标签 l 之后


当然,你是正确的,但这个输出并没有显示销毁调用的时机。也许在“l”标签周围添加cout会更好? - xtofl
@xtofl:好主意,我添加了更多的cout以澄清。 - Brian R. Bondy

1
其他答案是正确的......但是,如果你必须嵌套循环,我会质疑将它们放在那里的设计。将该逻辑拆分成单独的函数是解决这个问题的更好方法。
Billy3

是的,我也在质疑使用5D数组的逻辑,但有效就好。整个程序一开始就是一个巨大的循环,不断地对一组数据进行各种操作。 - Faken
天啊...一个5D数组?我唯一曾经需要它的理由是在PHP中,因为对于数据存储目的,数组和对象可能是相同的东西...说真的,在C语言中你需要一个5D数组干什么? - Matthew Scharley
因此,作为一个陌生人,请给它欢迎。天堂和地球上有更多的事情,霍雷肖,比你哲学中所梦想的还要多。 - Steve Jessop
@Mmatthew Scharley:我正在处理类似于位图图片的东西(但是不同于每个像素三种颜色的属性,而是更多科学项目中的一部分)。文件本身只是一个3D数组,但是当我需要一次处理方块块时,最终它变成了更像5D数组。2个维度用于粗x-y网格,然后另外2个维度用于粗网格中每个点的更好x-y位置,然后最后一个维度用于每个点的属性数据...说起来有点混乱。最糟糕的部分是弄清楚循环结构...那真是地狱... - Faken

0

倒退跳转会泄漏资源吗?或者以下代码还存在其他潜在问题吗?

重新执行:

        try
        {
            //Setup request
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);

            ....

            //Get Response
            HttpWebResponse response = (HttpWebResponse)request.GetResponse();

           if(response != HttpStatus.OK && noOfRetries < 3)
            {
                noOfRetries++;
                Thread.Sleep(10 * 1000);


                response.Close();
                goto Reexecute;
            }
            ...

            response.Close();
         }
         catch
         {

         }

0

不,你不会这样做。

然而,请确保任何外部资源都被正确释放。例如,如果你打开了一个文件,可能会跳过它通常关闭的位置。


0

不需要。局部变量不需要单独清理。当堆栈弹出时,所有局部变量也会随之消失。


0

不,如果您使用goto语句跳出循环,那么循环中的任何自动变量都不会导致编程泄漏。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接