C/C++: goto into the for loop

2020-06-09 11:47发布

问题:

I have a bit unusual situation - I want to use goto statement to jump into the loop, not to jump out from it.

There are strong reasons to do so - this code must be part of some function which makes some calculations after the first call, returns with request for new data and needs one more call to continue. Function pointers (obvious solution) can't be used because we need interoperability with code which does not support function pointers.

I want to know whether code below is safe, i.e. it will be correctly compiled by all standard-compliant C/C++ compilers (we need both C and 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...
    }
}

I've studied standards, but there isn't much information about such use case. I also wonder whether replacing for by equivalent while will be beneficial from the portability point of view.

Thanks in advance.

UPD: Thanks to all who've commented!

  1. to all commenters :) yes, I understand that I can't jump over initializers of local variables and that I have to save/restore i on each call.

  2. about strong reasons :) This code must implement reverse communication interface. Reverse communication is a coding pattern which tries to avoid using function pointers. Sometimes it have to be used because of legacy code which expects that you will use it.

Unfortunately, r-comm-interface can't be implemented in a nice way. You can't use function pointers and you can't easily split work into several functions.

回答1:

Seems perfectly legal.

From a draft of the C99 standard http://std.dkuug.dk/JTC1/SC22/WG14/www/docs/n843.htm in the section on the goto statement:

[#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
                   /* ... */
           }

Next, we look at the for loop statement:

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

Putting the two together with your problem tells you that you are jumping past

i=0;

into the middle of a while loop. You will execute

...process data...

and then

i++;

before flow of control jumps to the test in the while/for loop

i<n;


回答2:

Yes, that's legal.

What you're doing is nowhere near as ugly as e.g. Duff's Device, which also is standard-compliant.

As @Alexandre says, don't use goto to skip over variable declarations with non-trivial constructors.


I'm sure you're not expecting local variables to be preserved across calls, since automatic variable lifetime is so fundamental. If you need some state to be preserved, functors (function objects) would be a good choice (in C++). C++0x lambda syntax makes them even easier to build. In C you'll have no choice but to store state into some state block passed in by pointer by the caller.



回答3:

First, I need to say that you must reconsider doing this some other way. I've rarely seen someone using goto this days if not for error management.

But if you really want to stick with it, there are a few things you'll need to keep in mind:

  • Jumping from outside the loop to the middle won't make your code loop. (check the comments below for more info)

  • Be careful and don't use variables that are set before the label, for instance, referring to *data_to_request. This includes iwhich is set on the for statement and is not initialized when you jump to the label.

Personally, I think in this case I would rather duplicate the code for ...process data... then use goto. And if you pay close attention, you'll notice the return statement inside your for loop, meaning that the code of the label will never get executed unless there's a goto in the code to jump to it.

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


回答4:

No, you can't do this. I don't know what this will do exactly, but I do know that as soon as you return, your call stack is unwound and the variable i doesn't exist anymore.

I suggest refactoring. It looks like you're pretty much trying to build an iterator function similar to yield return in C#. Perhaps you could actually write a C++ iterator to do this?



回答5:

It seems to me that you didn't declare i. From the point of declaration completely depends whether or not this is legal what you are doing, but see below for the initialization

  • In C you may declare it before the loop or as loop variable. But if it is declared as loop variable its value will not be initialized when you use it, so this is undefined behavior. And if you declare it before the for the assignment of 0 to it will not be performed.
  • In C++ you can't jump across the constructor of the variable, so you must declare it before the goto.

In both languages you have a more important problem, this is if the value of i is well defined, and if it is initialized if that value makes sense.

Really if there is any way to avoid this, don't do it. Or if this is really, really, performance critical check the assembler if it really does what you want.



回答6:

If I understand correctly, you're trying to do something on the order of:

  • The first time foo is called, it needs to request some data from somewhere else, so it sets up that request and immediately returns;
  • On each subsequent call to foo, it processes the data from the previous request and sets up a new request;
  • This continues until foo has processed all the data.

I don't understand why you need the for loop at all in this case; you're only iterating through the loop once per call (if I understand the use case here). Unless i has been declared static, you lose its value each time through.

Why not define a type to maintain all the state (such as the current value of i) between function calls, and then define an interface around it to set/query whatever parameters you need:

typedef ... FooState;

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


回答7:

The initialisation part of the for loop will not occur, which makes it somewhat redundant. You need to initialise i before the goto.

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

However, this is really not a good idea!

The code is flawed in any case, the return statment circumvents the loop. As it stands it is equivalent to:

int i = 0 ;

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

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

In the end, if you think you need to do this then your design is flawed, and from the fragment posted your logic also.