初期化されていないローカル変数は最速の乱数ジェネレータですか?


329

初期化されていないローカル変数が未定義の動作(UB)であることを知っています。また、値にはトラップ表現があり、それが以降の操作に影響を与える可能性があります。ただし、乱数を視覚的表現にのみ使用し、それ以外の部分では使用しない場合もあります。たとえば、プログラムを視覚効果にランダムな色で設定します。次に例を示します。

void updateEffect(){
    for(int i=0;i<1000;i++){
        int r;
        int g;
        int b;
        star[i].setColor(r%255,g%255,b%255);
        bool isVisible;
        star[i].setVisible(isVisible);
    }
}

それよりも速いですか

void updateEffect(){
    for(int i=0;i<1000;i++){
        star[i].setColor(rand()%255,rand()%255,rand()%255);
        star[i].setVisible(rand()%2==0?true:false);
    }
}

また、他の乱数ジェネレータよりも高速ですか?


88
+1これは完全に正当な質問です。実際には、初期化されていない値ランダムである可能性あります。彼らは特にそうではなく、それがUBであるという事実は、悪いことを尋ねることにはなりません。
イマレット2015

35
@imallett:もちろんです。これは良い質問です。過去の少なくとも1つの古いZ80(Amstrad / ZX Spectrum)ゲームは、そのプログラムをデータとして使用して地形を設定しました。したがって、前例さえあります。最近はできません。最新のオペレーティングシステムは、すべての楽しみを奪います。
バトシェバ2015

81
確かに主な問題はそれがランダムではないということです。
ジョン

30
実際、初期化されていない変数がランダムな値として使用されている例があります。DebianRNG災害この記事の例4 )を参照てください
PaperBirdMaster 2015

31
実際には-そして私を信じて、私はさまざまなアーキテクチャで多くのデバッグを行います-あなたのソリューションは2つのことを行うことができます:初期化されていないレジスターを読み取るか、初期化されていないメモリを読み取ります。ここで「初期化されていない」とは、ある意味でランダムであることを意味しますが、実際には、a)ゼロb)繰り返しまたは一貫した値(以前にデジタルメディアによって占有されていたメモリを読み取る場合)、またはc)限られた値の一貫したゴミを含む可能性があり ます。セット(以前にエンコードされたデジタルデータによって占有されていたメモリを読み取る場合)。それらのどれも本当のエントロピー源ではありません。
mg30rg 2015

回答:


299

他の人が指摘したように、これは未定義の動作(UB)です。

実際には、(おそらく)実際には(一種の)動作します。x86 [-64]アーキテクチャで初期化されていないレジスタから読み取ると、実際にガベージ結果が生成され、おそらく悪いことは何も行われません(たとえば、Itaniumでは、レジスタに無効のフラグが付けられ、読み取りがNaNのようなエラーを伝播する可能性があります)。

ただし、主な問題が2つあります。

  1. 特にランダムではありません。 この場合、スタックから読み取っているので、以前そこにあったものをすべて取得します。これは事実上ランダムで完全に構造化されたもの、10分前に入力したパスワード、または祖母のCookieレシピである可能性があります。

  2. このようなことをコードに忍び込ませるのは悪いことです(大文字の 'B')。技術的にはreformat_hdd();、未定義の変数を読み取るたびにコンパイラーが挿入する可能性があります。それはしませんが、とにかくそれをするべきではありません。安全でないことをしないでください。例外が少ないほど、偶発的な間違いから常に安全になります。

UBのより差し迫った問題は、プログラム全体の動作が未定義になることです。最新のコンパイラはこれを使用して、コードの膨大な部分を排除したり、時間を遡ったりすることができます。UBで遊ぶことは、生きている原子炉を解体するビクトリア朝のエンジニアのようなものです。失敗することは無数にあり、基礎となる原理や実装されているテクノロジーの半分はおそらく分からないでしょう。それ大丈夫かもしれませんが、それをまだ起こさせてはいけません。詳細については、他の素晴らしい答えを見てください。

また、私はあなたを解雇します。


39
@Potatoswatter:Itaniumレジスタには、実質的に「初期化されていないレジスタ」であるNaT(Not Thing)を含めることができます。Itaniumでは、まだ書き込んでいないときにレジスタから読み取ると、プログラムが中止される場合があります(詳細については、blogs.msdn.com / b / oldnewthing / archive / 2004/01/19 / 60162.aspxを参照してください)。したがって、初期化されていない値を読み取ることが未定義の動作であるという正当な理由があります。Itaniumがあまり人気がない理由の1つでもあると
思います

58
私は「それは一種の作品」という概念に本当に反対しています。今日はそうであったとしてもそうでなかったとしても、より積極的なコンパイラーによりいつでも変更される可能性があります。コンパイラーは任意の読み取りをunreachable()プログラムで置き換え、プログラムの半分を削除できます。これは実際にも起こります。この動作により、一部のLinuxディストリビューションではRNGが完全に無効化されたと思います。; この質問のほとんどの回答は、初期化されていない値が値のように動作することを前提としているようです。それは誤りです。
usr

25
また、私はあなたが発砲するのはかなり愚かなことのように思われます。良い実践を想定すると、これはコードレビューで捕らえられ、議論され、二度と起こらないはずです。正しい警告フラグを使用しているので、これは間違いなくキャッチされるはずですよね?
Shafik Yaghmour 2015

17
@Michael実はそうです。プログラムのいずれかの時点で未定義の動作がある場合、コンパイラは、未定義の動作を呼び出す前のコードに影響を与えるようにプログラムを最適化できます。これがどれほど煩わしいかについて、さまざまな記事とデモンストレーションがあります。これは非常に優れたブログです。blogs.msdn.com / b / oldnewthing / archive / 2014/06/27 / 10537746.aspx(これには、プログラムのいずれかのパスがUBを呼び出した場合、すべての賭けは無効になります)
Tom Tanner '31

19
この答えは、「未定義の動作を呼び出すことは理論的には悪いが、実際にはそれほど害にはならない」かのように聞こえます。それは間違っている。UBを引き起こす式からエントロピーを収集すると、以前に収集されたすべてのエントロピーが失われる可能性があります(おそらくそうなるでしょ)。これは深刻な危険です。
Theodoros Chatzigiannakis

213

私はこれを明確に言いましょう:私たちはプログラムで未定義の振る舞いを起動しません。それは決して良い考えではありません。このルールにはまれな例外があります。たとえば、offsetofを実装するライブラリ実装者である場合などです。あなたのケースがそのような例外に該当する場合、あなたはおそらくこれをすでに知っています。この場合、初期化されていない自動変数の使用は未定義の動作であることがわかります

コンパイラは、未定義の動作に関する最適化により非常に攻撃的になり、未定義の動作がセキュリティ上の欠陥につながる多くのケースを見つけることができます。最も悪名高いのは、おそらくLinuxカーネルのnullポインターチェックの削除、C ++コンパイルのバグに対する私の回答で言及したものでしょうか。未定義の動作に関するコンパイラの最適化により、有限ループが無限ループに変わりました。

CERTの危険な最適化と因果関係の喪失ビデオ)を読むことができます。

コンパイラの作成者は、CおよびC ++プログラミング言語の未定義の動作を利用して最適化を向上させることがますます増えています。

多くの場合、これらの最適化は、開発者がソースコードに対して原因効果分析を実行する能力、つまり、以前の結果に対する下流の結果の依存性を分析する能力を妨げています。

その結果、これらの最適化はソフトウェアの因果関係を排除し、ソフトウェアの障害、欠陥、および脆弱性の可能性を高めています。

特に、不確定な値に関して、C標準の欠陥レポート451:初期化されていない自動変数の不安定性は、興味深い読み物になります。これはまだ解決されていませんが、値の不確定性がプログラム全体に伝播し、プログラム内の異なるポイントで異なる不確定値を持つ可能性があることを意味する、ぐらついた値の概念が導入されています。

これが発生する例は知りませんが、現時点では除外できません。

実際の例ではなく、期待した結果

ランダムな値を取得することはほとんどありません。コンパイラーはループを完全に最適化できます。たとえば、この単純化されたケースでは:

void updateEffect(int  arr[20]){
    for(int i=0;i<20;i++){
        int r ;    
        arr[i] = r ;
    }
}

clangはそれを最適化します(実際に見る):

updateEffect(int*):                     # @updateEffect(int*)
    retq

または、この変更されたケースのように、おそらくすべてゼロを取得します。

void updateEffect(int  arr[20]){
    for(int i=0;i<20;i++){
        int r ;    
        arr[i] = r%255 ;
    }
}

ライブでご覧ください

updateEffect(int*):                     # @updateEffect(int*)
    xorps   %xmm0, %xmm0
    movups  %xmm0, 64(%rdi)
    movups  %xmm0, 48(%rdi)
    movups  %xmm0, 32(%rdi)
    movups  %xmm0, 16(%rdi)
    movups  %xmm0, (%rdi)
    retq

これらのケースは両方とも、未定義の動作の完全に許容できる形式です。

Itaniumを使用している場合、トラップ値が発生する可能性があることに注意してください。

[...]レジスタが特別な「何もない」値を保持している場合、いくつかの命令を除いてレジスタトラップを読み取る[...]

その他の重要な注意事項

初期化されていないメモリに関して未定義の動作をどのように利用するかについて、UB Canariesプロジェクトで指摘されているgccとclangの違いに注目するのは興味深いことです。記事のノート(強調鉱山):

もちろん、そのような期待は言語標準とは何の関係もないことと、特定のコンパイラが何をするかとはすべて関係があることを自分自身で完全に明確にする必要があります。そのコンパイラのプロバイダーはそのUBを悪用したくない、または彼らがまだそれを利用することに慣れていないからです。コンパイラープロバイダーからの本当の保証が存在しない場合、まだ利用されていないUBは時限爆弾であると言いたいです。コンパイラーが少し積極的になるとき、彼らは来月または来年のオフを待っています。

Matthieu M.が指摘するように、すべてのCプログラマーが未定義の動作について知っておくべきこと#2/3もこの質問に関連しています。それは他のものの間で言う(強調鉱山):

実現するために重要と怖い事はということですちょうど約すべての 未定義の動作に基づいて最適化将来の任意の時点でバギーコードでトリガされ始めることができます。インライン化、ループのアンロール、メモリの昇格、その他の最適化は継続的に改善されます。それらが存在する理由の重要な部分は、上記のような2次的な最適化を公開することです。

私にとって、これは深く不満です。これは、コンパイラーが必然的に非難されてしまうためですが、Cコードの巨大な本体が爆発するのを待っているだけの地雷であることも意味します。

完全を期すために、実装は未定義の動作を明確に定義するように選択できることをおそらく言及する必要があります。たとえば、gccは共用体介した型パンニングを許可しますが、C ++ではこれは未定義の動作のように見えます。これが事実である場合、実装はそれを文書化する必要があり、これは通常は移植できません。


1
+(int)(PI / 3)コンパイラの出力例。実際の例UBであることを、よく、UB

2
UBを効果的に使用することは、優れたハッカーのトレードマークでした。この伝統はおそらく50年以上続いています。残念なことに、悪い人々のために、コンピューターは現在、UBの影響を最小限に抑える必要があります。OSがユーザーを自分自身から保護する能力を持っていなかったとき、私は90年代のUBマシンコードやポートの読み書きなどでクールなことをする方法を考え出すのを本当に楽しんだ。
sfdcfox 2015

1
@sfdcfoxをマシンコード/アセンブラで実行している場合は、未定義の動作ではありませんでした(従来とは異なる動作であった可能性があります)。
Caleth、2015

2
特定のアセンブリを念頭に置いている場合は、それを使用し、非準拠のCを記述しないでください。そうすれば、特定の移植性のないトリックを使用していることが誰でもわかるでしょう。そして、UBを使用できないのは悪い人ではなく、チップなどでトリックをしているIntelなどです。
Caleth

2
@ 500-InternalServerError。一般的なケースでは簡単に検出できないか、まったく検出できない可能性があるため、それらを拒否する方法はありません。これは、検出可能な文法違反です。また、形式が正しくない、または形式が正しくない診断は不要です。これにより、理論的に検出される可能性のある不適切に形成されたプログラムと、理論的には確実に検出されないプログラムとを区別できます。
Shafik Yaghmour 2015

164

いいえ、それはひどいです。

初期化されていない変数を使用した場合の動作は、CとC ++の両方で定義されておらず、そのようなスキームが望ましい統計的特性を持つことはほとんどありません。

「迅速で汚れた」乱数ジェネレータが必要な場合rand()は、最善の方法です。その実装では、乗算、加算、係数のみが実行されます。

私の知っている最速の発電機は、使用する必要がありuint32_t、擬似ランダム変数の型としてI、および使用

I = 1664525 * I + 1013904223

連続する値を生成します。あなたはあなたの空想をとるIシードと呼ばれる)の任意の初期値を選択することができます。もちろん、インラインでコーディングできます。符号なしの型の標準保証のラップアラウンドは、係数として機能します。(数値定数は、その驚くべき科学プログラマーであるドナルドクヌースによって厳選されています。)


9
提示する「線形合同」ジェネレーターは単純なアプリケーションに適していますが、非暗号化アプリケーションにのみ適しています。その挙動を予測することは可能です。たとえばドンクヌース自身による「線形合同暗号の解読」(IEEE Transactions on Information Theory、Volume 31)
Jay

24
@Jayは、迅速で汚いために、統一された変数と比較しましたか?これははるかに優れたソリューションです。
マイクマクマホン

2
rand()私の意見では、目的に適合しておらず、完全に廃止する必要があります。これらの日、あなたは非常に不良品を引き続き使用する必要は本当にありませんので、非常にほぼ同じ速やすさの最大であり、自由にライセンスを取得し、非常に優れた乱数発生器(例えばメルセンヌツイスター)をダウンロードすることができますrand()
ジャックAidley

1
rand()にはもう1つの恐ろしい問題があります。これは一種のロックを使用し、スレッド内で呼び出されるため、コードが大幅に遅くなります。少なくとも、再入可能なバージョンがあります。また、C ++ 11を使用する場合、ランダムAPIは必要なすべてを提供します。
Marwan Burelle、2015

4
公平を期すために、彼はそれが良い乱数発生器であるかどうか尋ねませんでした。彼はそれが速いかどうか尋ねました。まあ、はい、おそらく断食です。しかし、結果はまったくランダムではありません。
jcoder

42

良い質問!

未定義は、それがランダムであることを意味しません。考えてみてください。初期化されていないグローバル変数で取得する値は、システムまたは実行中のアプリケーションによって残されています。使用されなくなったメモリでのシステムの動作や、システムとアプリケーションが生成する値の種類に応じて、次のようになります。

  1. いつも同じ。
  2. 小さな値のセットの1つであること。
  3. 1つ以上の小さな範囲の値を取得します。
  4. 16/32/64ビットシステムのポインターから2/4/8で割り切れる多くの値を参照してください
  5. ...

得られる値は、システムやアプリケーションによって残されたランダムでない値に完全に依存します。したがって、確かにノイズが発生します(システムがメモリを使用しなくなった場合を除きます)が、そこから取得する値のプールは決してランダムではありません。

ローカル変数は、自分のプログラムのスタックから直接取得されるため、状況はさらに悪化します。プログラムが他のコードの実行中にこれらのスタック位置を実際に書き込む可能性は非常に高いです。私はこの状況で運の可能性が非常に低いと推定し、あなたが行った「ランダムな」コード変更がこの運を試みます。

ランダム性についてお読みください。見てわかるように、ランダム性は非常に特異的であり、プロパティを取得するのは困難です。追跡するのが難しい何か(提案など)を取るだけではランダムな値が得られると考えるのはよくある間違いです。


7
...そして、そのコードを完全に消化するコンパイラ最適化をすべて除外します。
Deduplicator

6 ...デバッグとリリースでは、「ランダムさ」が異なります。未定義はあなたがそれを間違っていることを意味します。
SQLサーファー

正しい。"undefined"!= "arbitrary"!= "random"で短縮または要約します。これらすべての種類の「不明」には、さまざまな特性があります。
2017年

グローバル変数は、明示的に初期化されているかどうかにかかわらず、定義された値を持つことが保証されています。 これはC ++Cでも同様です。
ブライアンヴァンデンバーグ

32

多くの良い答えがありますが、もう1つ追加して、決定論的コンピューターではランダムなものは何もないという点を強調しておきます。これは、疑似RNGによって生成された数値と、スタック上のC / C ++ローカル変数用に予約されたメモリ領域に見られる「ランダムな」数値の両方に当てはまります。

しかし...決定的な違いがあります。

優れた疑似乱数ジェネレーターによって生成された数値には、真にランダムな描画と統計的に類似する特性があります。たとえば、分布は均一です。サイクルの長さは長く、サイクルが繰り返される前に数百万の乱数を取得できます。シーケンスは自己相関ではありません。たとえば、2番目、3番目、または27番目の数値をすべて使用した場合、または生成された数値の特定の桁を調べた場合、奇妙なパターンが出現することはありません。

対照的に、スタックに残された「ランダムな」数には、これらの特性はありません。それらの値と見かけ上のランダム性は、プログラムの構築方法、プログラムのコンパイル方法、およびコンパイラーによる最適化方法に完全に依存します。例として、自己完結型プログラムとしてのアイデアのバリエーションを次に示します。

#include <stdio.h>

notrandom()
{
        int r, g, b;

        printf("R=%d, G=%d, B=%d", r&255, g&255, b&255);
}

int main(int argc, char *argv[])
{
        int i;
        for (i = 0; i < 10; i++)
        {
                notrandom();
                printf("\n");
        }

        return 0;
}

LinuxマシンでGCCを使用してこのコードをコンパイルし、実行すると、かなり不快に決定的であることがわかります。

R=0, G=19, B=0
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255

逆アセンブラでコンパイルされたコードを見ると、何が起こっているのかを詳細に再構築できます。notrandom()への最初の呼び出しは、このプログラムで以前に使用されなかったスタックの領域を使用しました。そこに何があったかを知っている人。しかし、notrandom()への呼び出しの後に、printf()への呼び出し(GCCコンパイラーは実際にはputchar()への呼び出しに最適化されますが、気にしないでください)があり、スタックが上書きされます。したがって、次回以降、notrandom()が呼び出されると、スタックにはputchar()の実行からの古いデータが含まれます。また、putchar()は常に同じ引数で呼び出されるため、この古いデータは常に同じです。あまりにも。

したがって、この振る舞いについてランダムなことは絶対にありません。また、このようにして得られた数値には、適切に作成された疑似乱数ジェネレータの望ましい特性がありません。実際、ほとんどの実際のシナリオでは、それらの値は反復的であり、高度に相関しています。

実際、他の人と同じように、私はこのアイデアを「高性能RNG」として偽装しようとした人物を解雇することも真剣に検討します。


1
「確定的コンピューターでは、ランダムなものは何もない」—これは実際には真実ではありません。現代のコンピューターには、個別のハードウェアジェネレーターなしで真の予測不可能なランダム性を生成できるあらゆる種類のセンサーが含まれています。現代のアーキテクチャでは、の値は/dev/randomそのようなハードウェアソースからシードされることが多く、実際には「量子ノイズ」です。つまり、言葉の物理的な意味で本当に予測不可能です。
Konrad Rudolph、

2
しかし、それは決定論的なコンピューターではありませんね。これで、環境入力に依存しています。いずれにせよ、これにより、初期化されていないメモリ内の従来の疑似RNG対「ランダム」ビットの説明をはるかに超えることができます。また、... / dev / randomの説明を見て、実装者が乱数が暗号的に安全であることを保証するためにどれだけ進んだかを理解してください...入力ソースが純粋な無相関の量子ノイズではないため、むしろ、ランダム性の程度が低い、潜在的に高度に相関しているセンサー測定値。それもかなり遅いです。
Viktor Toth 2015

29

未定義の動作とは、コンパイラーの作成者が問題を無視してもかまわないことを意味します。なぜなら、プログラマーは何が起こっても不満を言う権利を決して持っていないからです。

理論上、UBランドに入ると何かが起こる可能性がありますがノーズから飛び降りるデーモンを含む)、通常意味することは、コンパイラの作成者は気にせず、ローカル変数の場合、値はその時点でスタックメモリにあるものになります。

これはまた、多くの場合、コンテンツは「奇妙」ですが、固定またはわずかにランダムまたは可変ですが、明確なパターン(たとえば、各反復での値の増加)を伴います。

確かにあなたがすることはできません、それはまともな乱数発生器であることを期待しています。


28

未定義の動作は未定義です。未定義の値を取得することを意味するのではなく、プログラムが何でも実行でき、言語仕様を満たしていることを意味します。

良い最適化コンパイラは

void updateEffect(){
    for(int i=0;i<1000;i++){
        int r;
        int g;
        int b;
        star[i].setColor(r%255,g%255,b%255);
        bool isVisible;
        star[i].setVisible(isVisible);
    }
}

そして、それをnoopにコンパイルします。これは確かに他のどの方法よりも高速です。それは何もしないという欠点がありますが、それは未定義の振る舞いの欠点です。


3
コンパイラーの目的がプログラマーがドメイン要件を満たす実行可能ファイルを作成するのを助けるか、またはその目的がC規格の最小要件と一貫性のある動作をする最も「効率的な」実行可能ファイルを作成することであるかどうかに大きく依存します。そのような行動が何らかの有用な目的に役立つかどうかに関して。前者の目的に関しては、コードにr、g、bの任意の初期値を使用させるか、または可能であればデバッガートラップをトリガーする方が、コードをnopに変換するよりも便利です。後者の目的に関して...
スーパーキャット2015

2
...最適なコンパイラは、上記のメソッドを実行させる入力を決定し、そのような入力が受信された場合にのみ関連するコードを排除する必要があります。
スーパーキャット2015

1
@supercatまたはその目的は、Cが標準に準拠して効率的な実行可能ファイルを生成する一方で、プログラマーが準拠が役に立たない可能性がある場所を見つけるのを助けることです。コンパイラーは、GCCのように、標準が必要とするよりも多くの診断を出すことにより、この妥協の目的を満たすことができ-Wall -Wextraます。
Damian Yerrick、2015

1
値が未定義であることは、周囲のコードの動作が未定義であることを意味しません。コンパイラーはその関数を何も実行してはなりません。2つの関数呼び出しは、与えられた入力が何であれ、絶対に呼び出さなければなりません。最初は必ず0から255までの3つの数値で呼び出され、2番目は真または偽の値で呼び出されなければなりません(MUST)。「適切な最適化コンパイラ」は、関数のパラメータを任意の静的な値に最適化して、変数を完全に取り除くことができますが、それはそれが可能な限りです(まあ、関数自体が特定の入力で何もできない場合を除きます)。
Dewi Morgan、

@DewiMorgan-呼び出された関数は「このパラメーターを設定する」タイプであるため、入力がパラメーターの現在の値と同じである場合、コンパイラーはほぼ確実に何もしないようにします。
Jules

18

まだ言及されていませんが、未定義の動作を呼び出すコードパスは、コンパイラが望むすべてのことを実行できます。

void updateEffect(){}

これは、正しいループよりも確かに高速で、UBのため、完全に適合しています。


18

セキュリティ上の理由から、プログラムに割り当てられた新しいメモリをクリーンアップする必要があります。そうしないと、情報が使用され、パスワードがアプリケーション間で漏洩する可能性があります。メモリを再利用する場合にのみ、0とは異なる値が得られます。また、スタックの以前の値は固定されている可能性が非常に高いです。これは、そのメモリの以前の使用が固定されているためです。


13

あなたの特定のコード例はおそらくあなたが期待していることをしないでしょう。技術的にはループの各反復でr、g、およびbの値のローカル変数が再作成されますが、実際には、それはスタック上のまったく同じメモリ空間です。したがって、反復のたびに再ランダム化されることはなく、r、g、bが個別に、かつ最初にどれだけランダムであるかに関係なく、1000色のそれぞれに同じ3つの値を割り当てることになります。

実際、もしそれがうまくいったなら、何が再ランダム化されているのかについて私は非常に興味があります。私が考えることができる唯一のことは、そのスタックの上にピギーパックされたインターリーブされた割り込みであろう、非常にありそうもない。おそらく、レジスタをループ内のさらに下で再利用する実際のメモリ位置としてではなく、レジスタ変数として保持した内部最適化も、特に可視性の設定関数が特にレジスタを大量に消費する場合にうまくいきます。それでも、ランダムとはほど遠い。


12

ここのほとんどの人が未定義の振る舞いに言及したように。未定義は、有効な整数値を(幸運にも)取得できることも意味します。この場合、これはより高速になります(rand関数呼び出しが行われないため)。ただし、実際には使用しないでください。運がいつもあなたと一緒にいるわけではないので、これは恐ろしい結果になると確信しています。


1
とても良い点!それは実用的なトリックかもしれませんが、確かに運が必要なものです。
意味に関する事項

1
運は全く関係ありません。コンパイラーが未定義の動作を最適化しない場合、得られる値は完全に確定的です(=プログラム、その入力、コンパイラー、使用するライブラリー、スレッドがある場合はスレッドのタイミングに完全に依存します)。問題は、これらの値が実装の詳細に依存しているため、これらの値について推論できないことです。
cmaster-モニカを2015年

アプリケーションスタックとは別の割り込み処理スタックを備えたオペレーティングシステムがない場合は、割り込みが頻繁に現在のスタックの内容を超えてメモリの内容を妨害することが多いため、運が関係している可能性があります。
スーパーキャット2015

12

すごく悪い!悪い習慣、悪い結果。考慮してください:

A_Function_that_use_a_lot_the_Stack();
updateEffect();

関数A_Function_that_use_a_lot_the_Stack()が常に同じ初期化を行う場合、スタックには同じデータが残ります。そのデータは、私たちが呼び出すものupdateEffect()です。常に同じ値です!


11

私は非常に簡単なテストを行いましたが、それはまったくランダムではありませんでした。

#include <stdio.h>

int main() {

    int a;
    printf("%d\n", a);
    return 0;
}

私がプログラムを実行するたびに、同じ数(32767私の場合)が出力されました。これよりランダムにすることはできません。これはおそらく、ランタイムライブラリのスタートアップコードがスタックに残されたものです。プログラムが実行されるたびに同じ起動コードを使用し、プログラム間で実行間で何も変化しないため、結果は完全に一貫しています。


いい視点ね。結果は、この「乱数」ジェネレータがコードのどこで呼び出されるかに大きく依存します。ランダムというよりは予測不可能です。
NO_NAME 2015

10

「ランダム」の意味を定義する必要があります。賢明な定義では、取得する値にはほとんど相関関係がないはずです。それはあなたが測定できるものです。また、制御された再現可能な方法で達成することは簡単ではありません。したがって、未定義の動作は、あなたが探しているものではありません。


7

初期化されていないメモリが「unsigned char *」型[たとえばから返されるバッファmalloc] を使用して安全に読み取られる場合があります。コードは、コンパイラがウィンドウから因果関係をスローすることを心配する必要なく、そのようなメモリを読み取ることができます。また、初期化されていないデータが読み取られないようにするよりも、メモリに含まれる可能性があるものに対してコードを準備する方が効率的な場合があります(これの当たり前の例はmemcpy、意味のあるデータを含むすべての要素を個別にコピーするのではなく、部分的に初期化されたバッファーです。

ただし、そのような場合でも、バイトの任意の組み合わせが特に厄介なものである場合、それを読み取ると常にそのバイトのパターンが生成されると常に想定する必要があります(特定のパターンが本番環境では問題になりますが、開発では問題になります。パターンは、コードが本稼働するまで表示されません)。

初期化されていないメモリを読み取ることは、システムに最後に電源を入れてからメモリが実質的に非ランダムなコンテンツで書き込まれたことがないことを確認できる組み込みシステムのランダム生成戦略の一部として役立つかもしれません。メモリに使用されるプロセスは、電源投入時の状態を半ランダムに変化させます。すべてのデバイスが常に同じデータを生成する場合でもコードは機能するはずですが、たとえばノードのグループがそれぞれ任意の一意のIDをできるだけ迅速に選択する必要がある場合、半分のノードに同じ初期値を与える「それほどランダムではない」ジェネレーターがあります。 IDは、ランダム性の初期ソースをまったく持たないよりも優れている場合があります。


2
「バイトの任意の組み合わせが特に厄介なものである場合、それを読み取ると常にそのバイトのパターンが生成されます」-そのパターンに対処するようにコード化するまで、そのパターンはもう厄介ではなくなり、将来は別のパターンが読み取られます。
スティーブジェソップ2015

@SteveJessop:正確に。開発とプロダクションに関する私の説明は、同様の概念を伝えることを目的としていました。コードは、「いくつかのランダム性はいいかもしれない」という漠然とした概念を超えて、初期化されていないメモリの内容を気にする必要はありません。初期化されていない1つのメモリの内容によってプログラムの動作が影響を受ける場合、将来取得される部分の内容もその影響を受ける可能性があります。
スーパーキャット2015

5

他の人が言ったように、それは高速ですがランダムではありません。

ほとんどのコンパイラーがローカル変数に対して行うことは、スタック上にそれらのスペースを取得することですが、それを何かに設定することを迷惑をかけることはありません(標準は、それらが必要としないと言っているので、なぜあなたが生成しているコードを遅くしますか?)。

この場合、取得する値は、スタックの以前の状態によって異なります。100個のローカル文字変数がすべて「Q」に設定されている関数の前に関数を呼び出し、その後に関数を呼び出した場合それが戻ると、おそらく「ランダムな」値がmemset()「Q」に等しいかのように動作することがわかります。

重要なのは、これを使用しようとするサンプル関数の場合、これらの値は、読み取るたびに変更されることはなく、毎回同じになることです。したがって、100個の星がすべて同じ色と可視性に設定されます。

また、コンパイラーがこれらの値を初期化してはならないということは何もありません。将来のコンパイラーが初期化する可能性があります。

一般的には、悪い考えですが、しないでください。(多くの「賢い」コードレベルの最適化のように...)


2
あなたは何起こるかについていくつかの強力な予測を行っていますが、それはUBのために保証されているものではありません。また、実際には当てはまりません。
usr

3

他の人がすでに述べたように、これは未定義の動作(UB)ですが、「機能する」可能性があります。

他の人がすでに述べた問題を除いて、私はもう1つの問題(不利な点)を見つけました-CおよびC ++以外の言語では機能しません。私はこの質問がC ++についてであることを知っていますが、良いC ++とJavaコードになるコードを書くことができ、それが問題ではないのであれば、なぜでしょうか?たぶん、誰かがそれを他の言語に移植して、「手品」によって引き起こされたバグを探す必要があるかもしれませんならず、このような UBすることは間違いなく悪夢になります(特に経験の浅いC / C ++開発者にとって)。

ここでは、別の同様のUBに関する質問があります。このUBについて知らなくても、このようなバグを見つけようとしているところを想像してみてください。C / C ++でこのような奇妙なことについてさらに読みたい場合は、リンクからの質問に対する回答を読んで、この 素晴らしいスライドショーを参照してください。これは、内部の内容とその機能を理解するのに役立ちます。「魔法」でいっぱいの別のスライドショーではありません。ほとんどの経験豊富なC / c ++プログラマーでも、これから多くを学ぶことができると私は確信しています。


3

ない言語の未定義の振る舞いに関するロジックに依存するのは良い考えで。この投稿で言及/議論されたものに加えて、現代のC ++のアプローチ/スタイルでは、そのようなプログラムはコンパイルできない可能性があることを述べておきます。

これは、自動機能の利点とその便利なリンクを含む私の以前の投稿で言及されました。

https://stackoverflow.com/a/26170069/2724703

したがって、上記のコードを変更して実際の型をautoに置き換えると、プログラムはコンパイルさえできなくなります。

void updateEffect(){
    for(int i=0;i<1000;i++){
        auto r;
        auto g;
        auto b;
        star[i].setColor(r%255,g%255,b%255);
        auto isVisible;
        star[i].setVisible(isVisible);
    }
}

3

私はあなたの考え方が好きです。本当に箱の外。ただし、トレードオフは実際には価値がありません。メモリとランタイムのトレードオフは、ランタイムの未定義の動作を含むものではありません

ビジネスロジックとしてこのような「ランダム」を使用していることを知るのは、非常に不安な気持ちになるはずです。私はそれをしません。


3

使用7757あなたが初期化されていない変数を使用するように誘惑されているすべての場所を。素数のリストからランダムに選びました:

  1. 定義された動作です

  2. 常に0であるとは限らないことが保証されています

  3. それは素数です

  4. 初期化されていない変数と同じくらい統計的にランダムである可能性があります

  5. 値はコンパイル時にわかっているため、初期化されていない変数よりも高速になる可能性があります


比較については、この回答の結果を参照してください:stackoverflow.com/a/31836461/2963099
Glenn Teitelbaum

1

考慮すべきもう1つの可能性があります。

最新のコンパイラー(ahem g ++)は非常にインテリジェントなので、コードを調べて、どの命令が状態に影響し、何が影響しないかを確認します。命令が状態に影響しないことが保証されている場合、g ++は単にその命令を削除します。

だからここに何が起こるかです。g ++は間違いなく、あなたが読んでいること、算術を実行していること、保存していること、本質的にガベージ値であるものを認識します。新しいガベージが古いガベージよりも有用であるという保証はないので、ループがなくなるだけです。BLOOP!

この方法は便利ですが、ここでは私が何をするかです。UB(Undefined Behaviour)とrand()の速度を組み合わせます。

もちろん、rand()実行されるsを減らしますが、それらを混ぜ合わせて、コンパイラーが望まないことを何もしないようにします。

そして、私はあなたを解雇しません。


コンパイラがあなたのコードが愚かなことをしていると判断し、それを削除できると信じるのは非常に難しいと思います。未使用のコードを最適化するためだけに使用するのではなく、お勧めできないコードを期待します。再現可能なテストケースはありますか?いずれにしても、UBの推奨は危険です。さらに、GCCは唯一有能なコンパイラではありません。そのため、「モダン」として選別するのは不公平です。
underscore_d

-1

ランダム化のために初期化されていないデータを使用することは、適切に行われれば必ずしも悪いことではありません。実際、OpenSSLはまさにこれを行い、PRNGをシードします。

ただし、Valgrindが初期化されていないデータを使用して「修正」し、PRNGにバグを引き起こしていると不平を言っていることに誰かが気付いたため、この使用法は十分に文書化されていなかったようです。

したがって、それを行うことはできますが、自分が何をしているかを理解し、コードを読んでいる人がこれを理解できるようにする必要があります。


1
これは、未定義の動作で予期されるコンパイラーに依存することになります。私の回答からわかるように今日のclangは、彼らが望むことをしません。
Shafik Yaghmour 2015

6
OpenSSLがこのメソッドをエントロピー入力として使用したことは、それが良いことであるとは言いません。結局のところ、彼らが使用した他の唯一のエントロピーソースはPIDでした。正確なランダム値ではありません。このような悪いエントロピーソースに依存している人からは、他のエントロピーソースに対する適切な判断は期待できません。OpenSSLを現在維持している人々が明るくなることを願います。
cmaster-モニカを2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.