C / C ++:转到for循环

时间:2011-05-16 18:52:25

标签: c++ c compiler-construction goto

我有点不寻常的情况 - 我想使用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...
    }
}

我已经研究过标准,但是关于这种用例的信息并不多。我还想知道,从可移植性的角度来看,将替换为等同于,而是否有益。

提前致谢。

UPD :感谢所有评论过的人!

  1. 发给所有评论者:)是的,我知道我无法跳过局部变量的初始值设定项,并且我必须在每次调用时保存/恢复i

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

  3. 不幸的是,r-comm-interface无法以很好的方式实现。你不能使用函数指针,也不能轻易地将工作分成几个函数。

7 个答案:

答案 0 :(得分:10)

似乎完全合法。

来自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;

答案 1 :(得分:5)

是的,这是合法的。

你正在做的事情远没有像Duff的设备,也符合标准。

正如@Alexandre所说,不要使用goto来跳过使用非平凡构造函数的变量声明。


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

答案 2 :(得分:1)

首先,我需要说你必须重新考虑以其他方式这样做。如果不是error management,我很少见到有人使用 goto

但如果你真的想坚持下去,你需要记住一些事情:

  • 从循环外部跳到中间不会使代码循环。 (查看以下评论​​以获取更多信息)

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

我个人认为,在这种情况下,我宁愿复制...process data...的代码,然后使用goto。如果你密切注意,你会注意到中的返回语句循环,这意味着标签的代码将永远不会被执行,除非有一个goto代码跳转到它。

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

答案 3 :(得分:0)

不,你不能这样做。我不知道这会做什么,但我确实知道,一旦你返回,你的调用堆栈就会解开,变量i不再存在。

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

答案 4 :(得分:0)

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

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

在这两种语言中,您都有一个更重要的问题,即如果{{1}}的值定义得很好,并且如果该值有意义则初始化它。

真的如果有办法避免这种情况,请不要这样做。或者,如果确实如此,那么性能关键是检查汇编程序是否真的能够满足您的需求。

答案 5 :(得分:0)

for循环的初始化部分不会发生,这使它有点多余。您需要在转到之前初始化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...
}

然而, 确实不是一个好主意

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

int i = 0 ;

if( not_a_first_call )
    \\...process_data...

i++ ;
if( i < n )
{
    *data_to_request = i;
}

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

答案 6 :(得分:0)

如果我理解正确,你会尝试按以下顺序执行某些操作:

  • 第一次调用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);
  }
}