Подтвердить что ты не робот

Вывод из цикла из функции, вызванной в этом цикле

В настоящее время я пытаюсь выяснить способ выхода из цикла for из функции, вызванной в этом цикле. Я знаю о возможности просто вернуть функцию значение, а затем проверить на определенное значение и затем сломать, но я хотел бы сделать это изнутри функции напрямую. Это связано с тем, что я использую встроенную библиотеку для определенного аппаратного обеспечения, которое требует, чтобы функция-подпись моей функции выглядела следующим образом:

void foo (int passV, int aVal, long bVal)

Я знаю, что не использовать возвращаемое значение очень плохая практика, но, несмотря на все обстоятельства, меня заставляют, поэтому, пожалуйста, несите меня.

Рассмотрим следующий пример:

#include <stdio.h>

void foo (int a) {
    printf("a: %d", a);
    break;
}

int main(void) {
    for (int i = 0; i <= 100; i++) {
        foo(i);
    }
    return 0;
}

Теперь это не скомпилируется. Вместо этого я получаю ошибку компиляции следующим образом:

prog.c: В функции 'foo': prog.c: 6: 2: ошибка: оператор break не внутри цикла или коммутатора break;

Я знаю, что это значит (компилятор говорит, что разрыв в foo() не находится в цикле for)

Теперь, что я могу найти по стандарту относительно инструкции break, это:

Оператор break приводит к тому, что элемент управления переходит к следующему утверждению самое внутреннее приложение while, do, for или switch. синтаксис просто break;

Учитывая, что моя функция вызывается из цикла for, почему оператор break не выходит из указанного цикла? Кроме того, возможно ли реализовать что-то вроде этого без первой функции возврата?

4b9b3361

Ответ 1

break, например goto, может сканировать только локально внутри одной и той же функции, но если вам абсолютно необходимо, вы можете использовать setjmp и longjmp:

#include <stdio.h>
#include <setjmp.h>

jmp_buf jump_target;

void foo(void)
{
    printf("Inside foo!\n");
    longjmp(jump_target, 1);
    printf("Still inside foo!\n");
}

int main(void) {
    if (setjmp(jump_target) == 0)
        foo();
    else
        printf("Jumped out!\n");
    return 0;
}

Вызов longjmp приведет к возврату вызова setjmp. Возвращаемое значение из setjmp показывает, возвращается ли оно после установки цели перехода или если оно возвращается из скачка.

Вывод:

Inside foo!
Jumped out!

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

  • Так как longjmp перескакивает "через" все активизации функций между вызовом setjmp и вызовом longjmp, если какая-либо из этих функций ожидает выполнения дополнительной работы после текущего места выполнения, которые работают просто не будет сделано.
  • Если функция активации, которая вызвала setjmp, завершилась, поведение undefined. Все может случиться.
  • Если setjmp еще не вызывается, то jump_target не задано, а поведение undefined.
  • Локальные переменные в функции, которая называется setjmp, могут при определенных условиях иметь undefined значения.
  • Одно слово: темы.
  • Другие вещи, такие как флаги состояния с плавающей запятой, могут не сохраняться и что существуют ограничения на то, где вы можете поместить вызов setjmp.

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

Ответ 2

Вы не можете использовать break; таким образом, он должен появиться внутри тела цикла for.

Существует несколько способов сделать это, но не рекомендуется:

  • вы можете выйти из программы с помощью функции exit(). Поскольку цикл выполняется из main(), и после этого вы ничего не делаете, вы можете достичь того, чего хотите, но это как особый случай.

  • Вы можете установить глобальную переменную в функции и проверить, что в цикле for после вызова функции. Использование глобальных переменных обычно не рекомендуется.

  • вы можете использовать setjmp() и longjmp(), но это похоже на попытку выкачать муху молотком, вы можете сломать другие вещи и пропустить муху вообще. Я бы не рекомендовал этот подход. Кроме того, для этого требуется jmpbuf, чтобы вам пришлось перейти к функции или получить доступ как глобальную переменную.

Допустимой альтернативой является передача адреса переменной status в качестве дополнительного аргумента: функция может установить его, чтобы указать необходимость выхода из цикла.

Но, безусловно, лучший подход в C возвращает значение для проверки продолжения, это наиболее читаемый.

Из ваших объяснений у вас нет исходного кода для foo(), но вы можете обнаружить некоторые условия в функции, которую вы можете изменить, вызываемой прямо или косвенно с помощью foo(): longjmp() будет переходить из своего местоположения, глубоко внутри внутренних элементов foo(), возможно, много уровней в стеке вызовов, в местоположение setjmp(), минуя обычный код выхода функции для всех промежуточных вызовов. Если это именно то, что вам нужно сделать, чтобы избежать сбоя, setjmp()/longjmp() - это решение, но это может вызвать другие проблемы, такие как утечка ресурсов, отсутствие инициализации, несогласованное состояние и другие источники поведения undefined.

Обратите внимание, что ваш цикл for будет повторять 101 раз, потому что вы используете оператор <=. В идиоматическом цикле for используется for (int i = 0; i < 100; i++), чтобы точно и точно повторять количество раз, которое появляется как верхняя (исключенная) граница.

Ответ 3

(Примечание: вопрос был отредактирован, так как я изначально написал это)

Из-за того, как скомпилирован C, он должен знать, где разрываться, когда вызывается функция. Так как вы можете его вызывать из любого места или даже где-то перерыв не имеет смысла, вы не можете иметь оператор break; в своей функции и работать с ним следующим образом.

Другие ответы предложили такие ужасные решения, как установка глобальной переменной, используя #define или longjumping (!) из функции. Это очень плохие решения. Вместо этого вы должны использовать решение, которое вы неправильно отклоните в своем первом абзаце, и верните значение из вашей функции, которое указывает состояние, которое вы хотите вызвать break в этом случае, и выполните примерно следующее:

#include <stdbool.h>

bool checkAndDisplay(int n)
{
    printf("%d\n", n);
    return (n == 14);
}

int main(void) {
    for (int i = 0; i <= 100; i++) {
        if (checkAndDisplay(i))
            break;
    }
    return 0;
}

Попытка найти неясные способы достижения таких вещей вместо того, чтобы использовать правильный путь для достижения того же конечного результата, является верным способом генерации кода ужасного качества, который является кошмаром для поддержки и отладки.

Вы упомянули, скрытый в комментарии, что вы должны использовать возврат void, это не проблема, просто передайте параметр break в качестве указателя:

#include <stdbool.h>

void checkAndDisplay(int n, bool* wantBreak)
{
    printf("%d\n", n);
    if (n == 14)
        wantBreak = true;
}

int main(void) {
    bool wantBreak = false;
    for (int i = 0; i <= 100; i++) {
        checkAndDisplay(i, &wantBreak);
        if (wantBreak)
            break;
    }
    return 0;
}

Поскольку ваши параметры являются фиксированными, я предлагаю вам использовать бросок для передачи указателю на один из параметров, например. foo(a, b, (long)&out);

Ответ 4

break - это оператор, который разрешается во время компиляции. Поэтому компилятор должен найти подходящий для цикла while в той же функции. Обратите внимание, что нет гарантии, что функция не может быть вызвана из другого места.

Ответ 5

Если вы не можете использовать инструкцию break, вы можете определить локальную переменную в своем модуле и добавить второе условие выполнения в цикл for. Например, например, следующий код:

#include <stdio.h>
#include <stdbool.h>

static bool continueLoop = true;

void foo (int a)
{
    bool doBreak = true;

    printf("a: %d",a);

    if(doBreak == true){
        continueLoop = false;
    }
    else {
        continueLoop = true;
    }
}
int main(void) {
    continueLoop = true;   // Has to be true before entering the loop
    for (int i = 0; (i <= 100) && continueLoop; i++)
    {
        foo(i);
    }
    return 0;
}

Обратите внимание, что в этом примере это не точно break -instruction, но цикл for не будет выполнять другую итерацию. Если вы хотите сделать break, вам нужно вставить условие if с переменной continueLoop, которая приведет к break:

int main(void) {
    continueLoop = true;   // Has to be true before entering the loop
    for (int i = 0; i <= 100; i++)
    {
        foo(i);
        if(!continueLoop){
            break;
        }
    }
    return 0;
}

Ответ 6

Я считаю, что это связано с тем, как оператор break преобразуется в машинный код. Оператор break будет переведен как безусловная ветвь на метку сразу после цикла или коммутатора.

mov ECX,5
label1:
  jmp <to next instruction address>  ;break
loop label1
<next instruction>

Пока вызов foo() изнутри цикла приведет к чему-то вроде

mov ECX,5
label1:
  call <foo address>
loop label1
<next instruction>

и в foo адрес

call <printf address>
jmp <to where?> ;break cannot translate to any address.

Ответ 7

Это еще одна идея, которая может быть или не быть выполнимой: сохранить переменную вокруг, которая может превратить foo в no-op:

int broken = 0;

void foo (int a) {
    if (broken) return;

    printf("a: %d", a);
    broken = 1; // "break"
}

int main(void) {
    for (int i = 0; i <= 100; i++) {
        foo(i);
    }
    return 0;
}

Это функционально то же самое, за исключением некоторой потери тактовых циклов (функция будет вызываться, но выполнять только оператор if), и нет необходимости изменять цикл. Он не является потокобезопасным и работает только в первый раз (но foo может reset переменная broken равняться 0, если вызывается с a равным 0, если необходимо).

Так не велика, но идея, о которой еще не упоминалось.

Ответ 8

В таком случае рассмотрим использование цикла while() с несколькими условными операторами, привязанными к && вместо цикла for. Хотя вы можете изменить нормальный поток управления, используя такие функции, как setjmp и longjmp, он в значительной степени считается плохой практикой везде. Вам не нужно искать слишком много на этом сайте, чтобы узнать, почему. (Короче говоря, из-за его способности создавать свернутый поток управления, который не поддается ни отладке, ни человеческому пониманию)

Также подумайте над тем, чтобы сделать что-то вроде этого:

int foo (int a) {
    if(a == someCondition) return 0;
    else {
        printf("a: %d", a);
        return 1;
    }
}

int main(void) {
    for (int i = 0; i <= 100; i++) {
        if(!foo(i)) break;
    }
    return 0;
}

В этом случае цикл зависит от верного значения, возвращаемого из 'foo', которое разбивает цикл, если условие внутри 'foo' не выполняется.

Изменить: я не против использования goto, setjmp, longjmp и т.д. Но я думаю, что в этом случае существует гораздо более простое и более сжатое решение, не прибегая к этим мерам!

Ответ 9

Если вы не можете обрабатывать возвращаемые значения, можете ли вы хотя бы добавить параметр к функции: Я могу представить себе такое решение:

void main (void)
{
  int a = 0;

  for (; 1 != a;)
  {
    foo(x, &a);
  } 
}

void foo( int x, int * a)
{
  if (succeeded)
  {
    /* set the break condition*/
    *a = 1;
  }
  else
  {
    *a = 0;
  }
}

Это мой первый пост, поэтому, пожалуйста, простите меня, если мое форматирование перепутано:)

Ответ 10

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

#include <stdbool.h>

bool foo (int x)
{
    return (x==14);
}

void loopFoo(int passV, int aVal, long bVal)
{
   for (int i = 0; i <= 100; ++i)
   {
       if (foo(x))
           break;
   }
}

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

Ответ 11

Просто установите глобальную переменную и проверьте, что в цикле:

#include <stdio.h>

int leave = 0;

void foo (int a) {
    printf("a: %d", a);
    leave = 1;
}

int main(void) {
    for (int i = 0; i <= 100; i++) {
        foo(i);
        if (leave)
          break;
    }
    return 0;
}

Ответ 12

Считайте встроенную функцию вручную в цикле for. Если эта функция вызывается в нескольких циклах, определите ее как макрос:

#define f()\
printf("a: %d", a);\
break;

Ответ 13

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

#include <stdio.h>

void foo (int a) {
    printf("a: %d", a);
    if (a == 50)
    {
       throw a;
    }
}

int main(void) {
    try {
        for (int i = 0; i <= 100; i++) {
            foo(i);
        }
    catch(int e) {
    }
    return 0;
}