C / C ++: перейти в цикл for - PullRequest
12 голосов
/ 16 мая 2011

У меня немного необычная ситуация - я хочу использовать оператор 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. по веским причинам :) Этот код должен реализовывать интерфейс обратной связи.Обратная связь - это шаблон кодирования, который пытается избежать использования указателей функций.Иногда его приходится использовать из-за устаревшего кода, который предполагает, что вы будете использовать его.

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

Ответы [ 7 ]

10 голосов
/ 16 мая 2011

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

Из черновика стандарта 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
                   /* ... */
           }

Далее мы рассмотримоператор цикла:

[#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;
5 голосов
/ 16 мая 2011

Да, это законно.

То, что вы делаете, далеко не так ужасно, как, например. Устройство Даффа, которое также соответствует стандартам.

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


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

1 голос
/ 16 мая 2011

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

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

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

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

Лично я думаю, что в этом случае я бы лучше продублировал код для ...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; 
    }
}
0 голосов
/ 16 мая 2011

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

  • При первом вызове 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);
  }
}
0 голосов
/ 16 мая 2011

Инициализационная часть цикла 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;
}

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

0 голосов
/ 16 мая 2011

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

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

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

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

0 голосов
/ 16 мая 2011

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

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

...