C/С++: перейти в цикл for

У меня немного необычная ситуация: я хочу использовать оператор goto для перехода в в цикл, а не выпрыгивать из него.

Есть серьезные причины для этого - этот код должен быть частью некоторой функции, которая делает некоторые вычисления после первого вызова, возвращается с запросом на новые данные и требует еще одного вызова для продолжения. Указатели функций (очевидное решение) нельзя использовать, поскольку нам нужна совместимость с кодом, который не поддерживает указатели на функции.

Я хочу знать, безопасен ли код ниже, т.е. он будет правильно скомпилирован всеми стандартными компиляторами 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: Спасибо всем, кто прокомментировал!

  • всем комментаторам:) да, я понимаю, что я не могу перепрыгнуть через инициализаторы локальных переменных и что мне нужно сохранять/восстанавливать i для каждого вызова.

  • о сильных причинах:) Этот код должен реализовывать интерфейс обратной связи. Обратное сообщение - это шаблон кодирования, который пытается избежать использования указателей функций. Иногда он должен использоваться из-за устаревшего кода, который ожидает, что вы будете использовать его.

К сожалению, r-comm-интерфейс не может быть реализован красиво. Вы не можете использовать указатели на функции, и вы не можете легко разделить работу на несколько функций.

Ответ 1

Кажется совершенно законным.

Из черновика стандарта C99 http://std.dkuug.dk/JTC1/SC22/WG14/www/docs/n843.htm в разделе инструкции goto:

[#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 Device, которое также является стандартным.

Как говорит @Alexandre, не используйте goto, чтобы пропустить объявления переменных с нетривиальными конструкторами.


Я уверен, что вы не ожидаете сохранения локальных переменных во всех вызовах, поскольку автоматическое время жизни переменной настолько фундаментально. Если вам нужно сохранить какое-либо состояние, функторы (объекты функции) будут хорошим выбором (в С++). Синтаксис синтаксиса С++ 0x делает их еще проще для сборки. В C у вас не останется выбора, кроме как сохранить состояние в каком-либо состоянии, переданном указателем вызывающим.

Ответ 3

Нет, вы не можете этого сделать. Я не знаю, что это будет делать точно, но я знаю, что как только вы вернетесь, ваш стек вызовов разматывается, а переменная i больше не существует.

Я предлагаю рефакторинг. Похоже, вы пытаетесь создать итераторную функцию, аналогичную yield return в С#. Возможно, вы могли бы написать итератор С++ для этого?

Ответ 4

Мне кажется, что вы не объявили i. С точки зрения декларации полностью зависит от того, является ли это законным то, что вы делаете, но смотрите ниже для инициализации

  • В C вы можете объявить его перед циклом или переменной цикла. Но если он объявлен как переменная цикла, его значение не будет инициализировано при его использовании, поэтому это поведение undefined. И если вы объявите его перед for, назначение 0 ему не будет выполнено.
  • В С++ вы не можете перепрыгнуть через конструктор переменной, поэтому вы должны объявить ее перед goto.

В обоих языках у вас есть более важная проблема, это если значение i определено правильно, и если оно инициализируется, если это значение имеет смысл.

Действительно, если есть способ избежать этого, не делайте этого. Или, если это действительно так, критически важно проверить ассемблер, если он действительно делает то, что вы хотите.

Ответ 5

Во-первых, я должен сказать, что вы должны пересмотреть это сделать другим способом. Я редко видел, что кто-то использовал goto в наши дни, если не для управления ошибками.

Но если вы действительно хотите придерживаться этого, вам нужно запомнить несколько вещей:

  • Переход извне цикла в середину не сделает ваш цикл кода. (ознакомьтесь с комментариями ниже для получения дополнительной информации)

  • Будьте осторожны и не используйте переменные, установленные перед меткой, например, ссылаясь на *data_to_request. Это включает в себя i, который установлен в инструкции for и не инициализируется при переходе на метку.

Лично я считаю, что в этом случае я предпочел бы дублировать код для ...process data..., а затем использовать goto. И если вы обратите пристальное внимание, вы заметите оператор return внутри цикла for, что означает, что код метки никогда не будет выполнен, если в код не будет переход к нему.

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

Ответ 6

Параметр инициализации цикла for не будет возникать, что делает его несколько избыточным. Вам нужно инициализировать i до 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...
}

Однако, это действительно не очень хорошая идея!

В любом случае код является ошибочным, а статут возврата обходит цикл. В его нынешнем виде это эквивалентно:

int i = 0 ;

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

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

В конце концов, если вы считаете, что вам нужно это сделать, тогда ваш дизайн будет испорчен, а из фрагмента выведет вашу логику.

Ответ 7

Если я правильно понимаю, вы пытаетесь сделать что-то по порядку:

  • При первом вызове 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);
  }
}