Цикл for, который кажется практически бесконечным

82

Я сейчас отлаживаю какой-то код и наткнулся на эту строку:

for (std::size_t j = M; j <= M; --j)

(Написано моим начальником, который в отпуске.)

Мне это кажется действительно странным.

Что оно делает? На мой взгляд, это похоже на бесконечный цикл.

Майкл Баллок
источник
31
size_tбеззнаковый, поэтому гарантируется возврат к максимальному значению при попытке отказаться от нуля, завершая цикл. Тем не менее, все еще ужасный код.
BoBTFish,
4
@Bathsheba Что делать, если Mэто максимальное значение size_t? Вы все еще думаете, что это умно?
Thorsten Dittmar
8
@Barmar Не очень много не всплывает никогда , так что вероятность> 0. Это означает, что это решение глупо, если есть способы выразить его просто.
Торстен Диттмар,
38
Я не понимаю голосов против. Этот вопрос ясен - я не вижу, как его можно улучшить. Вопрос от нового пользователя действительно интересный !
Вирсавия,
17
#define TRUE FALSEи поехать в отпуск.
Лев,

Ответы:

68

std::size_tСтандарт C ++ гарантирует, что это unsignedтип. И если вы уменьшитеunsigned тип с 0, стандарт гарантирует, что результатом этого будет наибольшее значение для этого типа.

Это значение всегда больше или равно M1 цикла поэтому цикл завершается.

Таким образом, j <= Mприменительно к unsignedтипу это удобный способ сказать «запустите цикл до нуля, затем остановитесь».

Существуют альтернативы, такие как запуск на jодин больше, чем вы хотите, и даже использование оператора слайда for (std::size_t j = M + 1; j --> 0; ){ , которые, возможно, более понятны, хотя требуют большего набора текста. Я предполагаю, что есть один недостаток (кроме сбивающего с толку эффекта, который он производит при первой проверке), заключается в том, что он плохо переносится на языки без беззнаковых типов, такие как Java.

Также обратите внимание, что схема, которую выбрал ваш начальник, «заимствует» возможное значение из unsignedнабора: в этом случае случается, что Mнабор std::numeric_limits<std::size_t>::max()не будет иметь правильного поведения. Фактически, в этом случае цикл бесконечен . (Это то, что вы наблюдаете?) Вы должны вставить комментарий к этому эффекту в код и, возможно, даже подтвердить это конкретное условие.


1 При условии Mотсутствия std::numeric_limits<std::size_t>::max().

Вирсавия
источник
7
Я виню погоду.
Петух в
3
Если М , std::numeric_limits<std::size_t>::max()то M + 1будет равен нулю, и for (std::size_t j = M + 1; j --> 0; )цикл не будет цикл на всех.
Пит Киркхэм,
51
Не называйте это «оператором слайда». В C ++ такого оператора нет, это просто хитроумная обфускация.
You
26
@DimitarMirchev: Потому что это не оператор. Это два оператора с нечетным интервалом. Назовите это идиомой, если вы настаиваете на том, чтобы задавать собеседникам не относящиеся к делу вопросы (но в идеале спросите их, как они будут реализовывать эту функциональность, вместо того, чтобы спрашивать об «умном» синтаксисе).
You
1
Предполагая, что size_tэто 64 бита, потребуется несколько сотен лет, чтобы увидеть некорректное поведение в граничном случае. (За исключением случаев, когда оптимизатор может избавиться от цикла.)
Carsten S
27

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

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

Этим более безопасным вариантом (и, на мой взгляд, более читабельным, при сохранении жестких ограничений по объему) будет:

{
    std::size_t j = M;
    do {
        doSomethingWith(j);
    } while (j-- != 0);
}

В качестве примера см. Следующий код:

#include <iostream>
#include <cstdint>
#include <climits>
int main (void) {
    uint32_t quant = 0;
    unsigned short us = USHRT_MAX;
    std::cout << "Starting at " << us;
    do {
        quant++;
    } while (us-- != 0);
    std::cout << ", we would loop " << quant << " times.\n";
    return 0;
}

Это в основном то же самое с объектом, unsigned shortи вы можете видеть, что он обрабатывает каждое отдельное значение:

Starting at 65535, we would loop 65536 times.

Замена do..whileцикла в приведенном выше коде тем, что в основном сделал ваш босс, приведет к бесконечному циклу. Попробуйте и посмотрите:

for (unsigned int us2 = us; us2 <= us; --us2) {
    quant++;
}
Paxdiablo
источник
7
Теперь крайний случай 0. А почему бы и нет for(size_t j = M; j-- != 0; )?
LogicStuff
Да, это действительно страдает от того, что угловой случай может быть более частым, чем numeric_limits<size_t>::max().
Вирсавия,
7
@Logic et al, в предоставленном мной методе нет крайнего случая, я добавил код, чтобы показать это. С вашим намечаемым раствором, начальное значение M == 0приведет к элементу-не обрабатываются, поэтому он делает есть крайний случай. Использование моего do..whileметода постпроверки полностью исключает крайний случай. Если вы попробуете с помощью M == 1, вы увидите, что он выполняет и 1, и 0. Точно так же начните его с max_size_t(что бы это ни было), и он успешно запустится в этой точке, а затем уменьшится до нуля включительно.
paxdiablo
Интересно, что этот случай мотивирует использование того, что некоторые называют «неструктурированным кодом», потому что в нем используется « exitif», а именно { j =M; for(;;){ f(j); if( j == 0 )break; j -= 1; } }. Может даже потребоваться заменить на breaka, gotoесли вещи будут вложенными, поскольку C не имеет именованных циклов. Если «структурированный» означает «восприимчивый к рассуждениям о фрагментах», он структурирован (макет помогает!), А также, если он означает «восприимчивый к формальной проверке посредством рассуждений о предварительных и пост-условиях». Хотя в этом случае j--работает, стиль exitif может быть оправдан, когда требуется более сложный переход.
PJTraill
@PJTraill Я не могу сказать, что то, что вы говорите, структурировано, а то, что вы говорите, не структурировано. Рассуждения о do-while просты. Он не «использует» выход не более, чем неструктурированным способом, в отличие от while-do.
Филипси