Использование итератора цикла 'for' после того, как цикл существует в C

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

Например, я всегда это делал (ПРИМЕЧАНИЕ: наши стандарты кода запрещают использование ключевого слова "break" ):

int i, result;
bool done = false;
for (i=0; i<10 && !done; i++) {
    if (some_condition) {
        result = i;
        done = true;
    }
}
// Value of i may be undefined here

Теперь, очевидно, переменная result может быть удалена, если я могу положиться на значение i. Я думал, что из-за оптимизации компилятора вы не можете полагаться на значение итератора цикла. Я просто помню обучение phantom? Или это стандарт (в частности, относительно GNU C)?

Ответ 1

Нет ничего плохого в C89, C99, или C11, чтобы получить доступ к переменной итерации после инструкции for.

 int i;

 for (i = 0; i < 10; i++) {
     /* Some code */
 }

 printf("%d\n", i);  // No magic, the value is 10

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

Ответ 2

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

Ответ 3

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

/*find the index of the first element which is odd*/
for (ii = 0; ii < nelem && arry[ii] % 2 == 0; ii++);

Как уже упоминалось, точка путаницы может исходить от конструкций, в которых сам итератор определен в инструкции for.

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

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

#include <stdio.h>
int main(void)
{
    int cur, ii = 0, nelem, arry [] = { 1, 2, 4, 6, 8, 8, 3, 42, 45, 67 };
    int sum = 0;

    nelem = sizeof(arry) / sizeof(int);
    /* Look mom! no curly braces! */

    for (
            ii = 0;
            ii < nelem && ((cur = arry[ii]) %2 == 0 ||
                                ((printf("Found odd number: %d\n", cur)||1)));
            ii++, sum += cur
        );
    printf("Sum of all numbers is %d\n", sum);
    return 0;
}

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

Ответ 4

Несмотря на то, что значение этой переменной управления циклом for хорошо определено, вам, возможно, сказали, что следует избегать использования переменной управления циклом for после цикла for из-за того, как обрабатывается область видимости этой переменной и особенно потому, что обработка изменила историю С++ (я знаю, что этот вопрос отмечен как "C", но я думаю, что обоснование для избежания использования переменной управления циклом после цикла может иметь происхождение в этой истории С++).

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

int more_work_to_do(void) 
{
    return 1;
}

int some_condition(void)
{
    return 1;
}

int foo()
{
    int i = 100;

    while (more_work_to_do()) {
        int done = 0;

        for (int i = 0; i < 10 && !done; i++) {
            if (some_condition()) {
                done = 1;
            }
        }

        if (done) return i;   // which `i`?
    }

    return 1;
}

При некоторых старых правилах определения для i, объявленных в цикле for, значение, возвращаемое в выражении с комментарием, "которое i" будет определяться циклом for (VС++ 6 использует эти правила). В соответствии с новыми стандартными правилами для определения этой переменной возвращается значение i, объявленное в начале функции.

Ответ 5

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

for (i=0u; (i<someLimit) && (found != TRUE); i++)
{
    if (someCondition) found = TRUE;
}
foundIndex = i-1;

В принципе, код, подобный этому, записывается, когда ключевое слово break не разрешено некоторыми правилами кодирования, например. на основе MISRA. Если вы не выходите из цикла, цикл, как правило, оставляет вас с "i", который отключен одним из того, что вам нужно.

Иногда вы можете даже найти это:

for (i=0u; (i<someLimit) && (found != TRUE); i++)
{
    if (someCondition) found = TRUE;
}
foundIndex = i;

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