【问题标题】:C/C++: goto into the for loopC/C++:进入 for 循环
【发布时间】:2011-05-16 18:52:25
【问题描述】:

我有一点不寻常的情况 - 我想使用 goto 语句来跳进入循环,而不是从中跳出。

这样做有充分的理由 - 此代码必须是某个函数的一部分,该函数在第一次调用后进行一些计算,返回新数据请求并需要再次调用才能继续。不能使用函数指针(显而易见的解决方案),因为我们需要与不支持函数指针的代码互操作。

我想知道下面的代码是否安全,即它会被所有符合标准的 C/C++ 编译器正确编译(我们需要 C 和 C++)。

function foo(int not_a_first_call, int *data_to_request, ...other parameters... )
{
    if( not_a_first_call )
        goto request_handler;
    for(i=0; i<n; i++)
    {
        *data_to_request = i;
        return;
request_handler:
        ...process data...
    }
}

我研究过标准,但没有太多关于此类用例的信息。我还想知道从可移植性的角度来看,将 for 替换为等效的 while 是否有益。

提前致谢。

UPD:感谢所有发表评论的人!

  1. 致所有评论者 :) 是的,我知道我不能跳过局部变量的初始化程序,而且我必须在每次调用时保存/恢复 i。

  2. 关于强项的原因:) 这段代码必须实现反向通信接口。反向通信是一种试图避免使用函数指针的编码模式。有时必须使用它,因为遗留代码希望您将使用它。

不幸的是,r-comm-interface 不能很好地实现。您不能使用函数指针,也不能轻易地将工作拆分为多个函数。

【问题讨论】:

  • “这样做的充分理由”——我没有在这篇文章中看到它们。
  • 你能不能把request_handler下面的东西放在一个单独的函数里然后调用这个函数?
  • 如果你声明了for循环之前标签的局部变量,那么使用这些变量的表达式的行为是未定义的。实际上,我敢打赌,关于堆栈状态的任何假设都是没有实际意义的。您显然正在这里寻找 协程,也许有这个词的一些 google 可以帮助您。
  • 必填:xkcd.com/292
  • 我知道这是旧的,但只是好奇你为什么不只是调用一个函数(不是函数指针,因为你说只是一个普通函数来执行相同的任务)?也许我没有正确理解?

标签: c++ c compiler-construction goto


【解决方案1】:

看起来完全合法。

来自 goto 语句部分中的 C99 标准草案http://std.dkuug.dk/JTC1/SC22/WG14/www/docs/n843.htm:

[#3] EXAMPLE 1 It is sometimes convenient to jump  into  the
   middle  of  a  complicated set of statements.  The following
   outline presents one possible approach to a problem based on
   these three assumptions:

     1.  The  general initialization code accesses objects only
         visible to the current function.

     2.  The  general  initialization  code  is  too  large  to
         warrant duplication.

     3.  The  code  to  determine  the next operation is at the
         head of the loop.  (To  allow  it  to  be  reached  by
         continue statements, for example.)

           /* ... */
           goto first_time;
           for (;;) {
                   // determine next operation
                   /* ... */
                   if (need to reinitialize) {
                           // reinitialize-only code
                           /* ... */
                   first_time:
                           // general initialization code
                           /* ... */
                           continue;
                   }
                   // handle other operations
                   /* ... */
           }

接下来,我们看一下for循环语句:

[#1]  Except for the behavior of a continue statement in the |
   loop body, the statement

           for ( clause-1 ; expr-2 ; expr-3 ) statement

   and the sequence of statements

           {
                   clause-1 ;
                   while ( expr-2 ) {
                           statement
                           expr-3 ;
                   }
           }

将两者与您的问题放在一起告诉您您正在跳过

i=0;

进入while循环的中间。你会执行

...process data...

然后

i++;

在控制流跳转到 while/for 循环中的测试之前

i<n;

【讨论】:

    【解决方案2】:

    是的,这是合法的。

    您正在做的事情远没有例如丑陋。 Duff 的设备,也符合标准。

    正如@Alexandre 所说,不要使用goto 跳过具有重要构造函数的变量声明。


    我敢肯定,您不会期望局部变量在调用中被保留,因为自动变量生命周期是如此重要。如果您需要保留一些状态,函子(函数对象)将是一个不错的选择(在 C++ 中)。 C++0x lambda 语法使它们更容易构建。在 C 中,您别无选择,只能将状态存储到调用者通过指针传入的某个状态块中。

    【讨论】:

    • 这可能是合法的,但我仍然认为这是一件非常奇怪的事情
    • 达夫的设备不使用goto。
    • Duff 的设备使用 switch,这是 goto 的一种形式。致 Tony The Tiger:我已经更新了最初的问题,说明了这种解决方案背后的一些原因。
    • @Sergey, @Sjoerd:相关的事实是 Duff 的设备也跳到了循环的中间。这很丑,因为switch(case 标签)的语法结构和循环混合在一起。这也是美丽:)
    【解决方案3】:

    首先,我需要说您必须重新考虑以其他方式执行此操作。如果不是 error management,我现在很少看到有人使用 goto。

    但如果你真的想坚持下去,你需要记住以下几点:

    • 从循环外部跳到中间不会使您的代码循环。 (查看下面的 cmets 了解更多信息)

    • 小心不要使用设置在标签之前的变量,例如引用*data_to_request。这包括ii,它是在for 语句中设置的,并且在您跳转到标签时不会被初始化。

    就个人而言,我认为在这种情况下,我宁愿复制...process data... 的代码,然后使用 goto。如果你仔细观察,你会注意到 for 循环中的 return 语句,这意味着标签的代码将永远不会被执行,除非在跳转到它的代码。

    function foo(int not_a_first_call, int *data_to_request, ...other parameters... )
    {
        int i = 0;
        if( not_a_first_call )
        {
            ...process data...
            *data_to_request = i;
            return;
        }
    
        for (i=0; i<n; i++)
        {
            *data_to_request = i;
            return; 
        }
    }
    

    【讨论】:

    • 在这种特定情况下,用户只是在for循环中初始化了i,并没有被执行。很可能 i 将存储不满足循环条件的垃圾。更新了代码。
    • 返回不会产生问题,因为循环之前的行似乎在每种情况下都将流量重定向到返回。至于为什么会有一个不会执行额外循环的for循环?好吧,它至少可以在可能设置 *data_to_request 之前提供测试 (i
    • 似乎会重定向流量。我通常不做假设。事实是...... OP 为我们编写了一段糟糕的代码来帮助他。
    【解决方案4】:

    不,你不能这样做。我不知道这到底会做什么,但我知道,一旦您返回,您的调用堆栈就会展开,并且变量 i 不再存在。

    我建议重构。看起来您非常想在 C# 中构建类似于 yield return 的迭代器函数。也许您实际上可以编写一个 C++ 迭代器来执行此操作?

    【讨论】:

    • 我假设i是一个参数,因为没有本地定义,表明有附加参数。
    • 是的,我知道我必须恢复i 的值。在实际代码中,它与参数一起传递,打包在特殊结构中,并在每次调用时保存-恢复。
    【解决方案5】:

    在我看来你没有声明i。从声明的角度来看,这完全取决于您在做什么合法,但请参阅下面的初始化

    • 在 C 语言中,您可以在循环之前声明它或将其声明为循环变量。但是如果它被声明为循环变量,它的值在你使用时不会被初始化,所以这是未定义的行为。如果你在for 之前声明它,0 的赋值将不会被执行。
    • 在 C++ 中,您不能跳过变量的构造函数,因此您必须在 goto 之前声明它。

    在这两种语言中,您有一个更重要的问题,即 i 的值是否定义良好,并且如果该值有意义则初始化。

    真的,如果有任何方法可以避免这种情况,请不要这样做。或者,如果这真的非常非常关键,请检查汇编器是否真的能满足您的要求。

    【讨论】:

      【解决方案6】:

      如果我理解正确,您正在尝试按以下顺序做某事:

      • 第一次调用foo,它需要从其他地方请求一些数据,所以它设置了那个请求并立即返回;
      • 在每次后续调用 foo 时,它都会处理来自前一个请求的数据并设置一个新请求;
      • 这会一直持续到foo 处理完所有数据。

      我不明白为什么在这种情况下你需要for 循环;每次调用你只迭代一次循环(如果我理解这里的用例)。除非i 被声明为static,否则你每次都会失去它的价值。

      为什么不定义一个类型来维护函数调用之间的所有状态(例如i 的当前值),然后围绕它定义一个接口来设置/查询您需要的任何参数:

      typedef ... FooState;
      
      void foo(FooState *state, ...)
      {
        if (FirstCall(state))
        {
          SetRequest(state, 1);
        }
        else if (!Done(state))
        {
          // process data;
          SetRequest(state, GetRequest(state) + 1);
        }
      }
      

      【讨论】:

      • i 的值不一定会丢失,因为它可能在其他地方声明,可能是一个全局变量。此外,i
      【解决方案7】:

      for 循环的初始化部分不会发生,这使得它有些多余。您需要在 goto 之前初始化 i。

      int i = 0 ;
      if( not_a_first_call )
          goto request_handler;
      for( ; i<n; i++)
      {
          *data_to_request = i;
          return;
      request_handler:
          ...process data...
      }
      

      但是,这真的不是一个好主意!

      代码在任何情况下都是有缺陷的,return 语句绕过了循环。就目前而言,它相当于:

      int i = 0 ;
      
      if( not_a_first_call )
          \\...process_data...
      
      i++ ;
      if( i < n )
      {
          *data_to_request = i;
      }
      

      最后,如果您认为需要这样做,那么您的设计就有缺陷,并且从片段中也发布了您的逻辑。

      【讨论】:

      • 不,“..process_data..”在循环内。
      • 在... process_data 部分之后是循环增量i++。
      • @Jose_X:是的,它是,但它不必是等价的。浏览代码; ...process data... 仅在 not_a_first_call 为真时执行,并且 before 因为 goto 而处理 for 循环语句。然后进行i++和i&lt;n测试,分配*data_to_request,函数无条件返回,所以没有循环!
      • @larsmans:好地方(已编辑),但我想知道它是否真的重要?这取决于i 是否真的在这个函数之外并且忽略初始化是有意的,同样取决于n 的值。如果i 和n 确实在函数外部,那么这段代码的问题不仅仅是滥用goto 和for 循环!
      • 这取决于n 的值。它是否“真的很重要”取决于 OP 是否真的对这段(请原谅我的语言)粗鲁的意大利面条代码有用。
      猜你喜欢
      • 2021-12-12
      • 1970-01-01
      • 2011-09-22
      • 2014-04-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-04-19
      相关资源
      最近更新 更多