実質的に無限に見える「for」ループ


82

私は現在いくつかのコードをデバッグしています、そして私はこの行に出くわしました:

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

(休暇中の上司によって書かれました。)

それは私には本当に奇妙に見えます。

それは何をするためのものか?私には、それは無限ループのように見えます。


31
size_tは符号なしであるため、ゼロを非推奨にしようとしたときに最大値にラップアラウンドしてループを終了することが保証されています。それでも恐ろしいコードです。
BoBTFish 2016

4
@BathshebaMたまたまの最大値になった場合はsize_tどうなりますか?あなたはまだそれが賢いと思いますか?
Thorsten Dittmar 2016

8
@Barmarんがあまり来ていないされていないに来ることはありませんので、確率>それは簡単な配置する方法がある場合は、このソリューションが愚かであることを意味0があります。
Thorsten Dittmar 2016

38
反対票がわかりません。この質問は明らかです-私はそれがどのように改善されることができるかわかりません。新しいユーザーから、この質問は実際に興味深いものです!
バトシェバ2016

17
#define TRUE FALSEそして休日に行きます。
Leo Heinsaar 2016

回答:


68

std::size_tC ++標準によってunsignedタイプであることが保証されています。また、unsignedタイプを0からデクリメントすると、標準では、その結果がそのタイプの最大値になることが保証されます。

そのラップアラウンド値は常にM1以上であるため、ループは終了します。

したがってj <= Munsigned型に適用する場合は、「ループをゼロまで実行してから停止する」という便利な方法です。

j必要以上に大きく実行したり、スライド演算子 を使用したりするなどの代替手段for (std::size_t j = M + 1; j --> 0; ){があります。これらは、より多くの入力が必要ですが、ほぼ間違いなく明確です。ただし、(最初の検査で発生する当惑する効果を除いて)1つの欠点は、Javaなどの符号なし型のない言語にうまく移植できないことだと思います。

上司が選択したスキームは、unsignedセットから可能な値を「借用」することにも注意してください。この場合、Mセットstd::numeric_limits<std::size_t>::max()が正しい動作をしないことが発生します。実際、その場合、ループは無限大です。(それはあなたが観察していることですか?)あなたはコードにその効果へのコメントを挿入するべきであり、そしておそらくその特定の条件で主張するべきです。


1Mないことを条件としstd::numeric_limits<std::size_t>::max()ます。


7
天気のせいです。
帽子をかぶったオンドリ2016

3
Mであればstd::numeric_limits<std::size_t>::max()、その後M + 1はゼロになり、for (std::size_t j = M + 1; j --> 0; )すべてのループになるではないループ。
ピートカーカム2016

51
「スライド演算子」とは呼ばないでください。C ++にはそのような演算子はなく、見た目が巧妙な難読化にすぎません。
あなた

26
@DimitarMirchev:演算子ではないからです。間隔が奇数の2つの演算子です。インタビュイーに無関係な質問をすることを主張する場合は、それをイディオムと呼びます(ただし、理想的には、「巧妙な」構文について尋ねるのではなく、機能をどのように実装するかを尋ねます)。
あなた

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

7
さて、エッジケースは0です。なぜfor(size_t j = M; j-- != 0; )ですか?
logicStuff 2016

はい、これはおそらくより頻繁なコーナーケースに悩まされていnumeric_limits<size_t>::max()ます。
バトシェバ2016

7
@Logic et al、私が提供したメソッドにはエッジケースはありません。これを示すコードを追加しました。あなたと提案したソリューションの初期値は、M == 0要素0処理し、それがされていないになります、エッジケースを持っています。私のポストチェックdo..while方法を使用すると、エッジケースが完全に排除されます。で試してみるとM == 1、10の両方が実行されることがわかります。同様に、max_size_t(それが何であれ)で開始すると、その時点で正常に開始され、ゼロまでデクリメントされます。
paxdiablo 2016

興味深いことに、このケースは、「exitif」、つまりを使用しているため、「構造化コードではない」と言う人の使用を動機付けます{ j =M; for(;;){ f(j); if( j == 0 )break; j -= 1; } }。Cには名前付きループがないため、ネストされbreakgoto場合は、をaに置き換える必要がある場合もあります。「構造化」が「チャンクについての推論の影響を受けやすい」を意味する場合、それは構造化されており(レイアウトが役立ちます!)、また「事前条件と事後条件についての推論による正式な検証の影響を受けやすい」ことを意味します。この場合はj--機能しますが、より複雑な遷移が必要な場合は、exitifスタイルが正当化される可能性があります。
PJTraill 2016

@PJTraillあなたが言っていることが構造化されているのか、あなたが言っていることが構造化されていないのかわかりません。do-whileについての推論は簡単です。出口を「使用」することはありませんが、構造化されていない方法で使用することはありません。
philipxy 2016
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.