2016-07-01 16 views
4

この質問のタイトルの曖昧さを残念に思っていますが、これを正確に求める方法がわかりません。初期化intが関数の戻り値に影響する

次のコードは、Arduinoマイクロプロセッサ(ATMega328マイクロプロセッサ用にコンパイルされたC++)で実行すると問題なく動作します。戻り値はコード内のコメントに表示されます。

// Return the index of the first semicolon in a string 
int detectSemicolon(const char* str) { 

    int i = 0; 

    Serial.print("i = "); 
    Serial.println(i); // prints "i = 0" 

    while (i <= strlen(str)) { 
     if (str[i] == ';') { 
      Serial.print("Found at i = "); 
      Serial.println(i); // prints "Found at i = 2" 
      return i; 
     } 
     i++; 
    } 

    Serial.println("Error"); // Does not execute 
    return -999; 
} 

void main() { 
    Serial.begin(250000); 
    Serial.println(detectSemicolon("TE;ST")); // Prints "2" 
} 

これは、最初のセミコロンの位置として「2」を出力します。

ただし、detectSemicolon関数の最初の行をint i;に変更した場合、つまり明示的な初期化がないと問題が発生します。具体的には、出力は「i = 0」(良好)、「i = 2で見つかった」(良好)、「-999」(不良!)です。

したがって、行の直前にprintステートメントを実行していないにもかかわらず、return 2;行の直前でprintステートメントを実行したにもかかわらず、関数は-999を返しています。

ここで何が起こっているのか理解してくれる人がいますか?私は、彼らが初期化している場合を除き、Cの関数内の変数は、理論的には任意の古いがらくたを含めることができることを理解し、しかし、ここで私は、具体的...まだこれが起こっていないことをprint文のチェックイン、およびてる


編集:すべての人におかげで、特に彼らの偉大な答えのためのアンダースコアに感謝します。未定義の振る舞いのように見えますが、実際にはコンパイラはiを含む何もスキップしません。コメントアウトここdetectSemicolon内serial.printsとアセンブリの一部です:

void setup() { 
    Serial.begin(250000); 
    Serial.println(detectSemicolon("TE;ST")); // Prints "2" 
    d0: 4a e0   ldi r20, 0x0A ; 10 
    d2: 50 e0   ldi r21, 0x00 ; 0 
    d4: 69 e1   ldi r22, 0x19 ; 25 
    d6: 7c ef   ldi r23, 0xFC ; 252 
    d8: 82 e2   ldi r24, 0x22 ; 34 
    da: 91 e0   ldi r25, 0x01 ; 1 
    dc: 0c 94 3d 03  jmp 0x67a ; 0x67a <_ZN5Print7printlnEii> 

をそれは、コンパイラが実際に完全にwhileループを無視し、出力は常に「-999」になると結論づけているように見える、とそれ0xFC19をハードコーディングする代わりに、関数呼び出しを気にする必要はありません。 serial.printsを有効にして、関数が呼び出されるようにもう一度見ていきますが、これは私が思う強いポインターです。


EDIT 2:

https://justpaste.it/vwu8

あなたが見れば:本当に気に人のために

は、ここ(UBの場合)上図のとおりに逆アセンブルコードへのリンクです慎重に、コンパイラは、レジスタ012を、iの位置として指定しており、d8の0に "初期化"しているようです。このレジスタはwhileループ、if文などでは全体としてiが含まれているかのように扱われ、コードが正常に動作し、printステートメントが期待通りに出力されます(たとえば、行122では "i"がインクリメントされます)。

しかし、この擬似変数を返すことについては、私たちの試行錯誤したコンパイラにとってあまりにも遠い段階です。それはラインを描画し、他のreturn文にダンプします(ライン120はライン132にジャンプし、レジスタ24と25に "-999"をロードしてからmain()に戻ります)。

少なくとも、それは私の限られたアセンブリの把握で得ることができる限りです。コードの振る舞いが定義されていないとき、物語の道徳は奇妙なことが起こります。

+0

これだけの機能から1つの戻りのポイントを持っていることをお勧めします。コードをリファクタリングすることをお勧めします。また、初期化されていない変数を決して使用しないことを指摘している人もいます。それが使用される前に他の場所で初期化されることが確実であれば、宣言されている場所を必ず初期化する必要はありません。 –

+0

@RealtimeRik私は、多くの人が複数の「返品」ポイントについて不平を言うのを聞いたことがありません。これをアドバイスしている信頼できるエキスパートが記事を指すことができますか?私は、ここを出るのがはるかにクリーンな方法のように思えます。代わりに何をお勧めしますか? 'goto'? –

+0

Err、not a goto。彼らはそこに使用しているが、それほど遠くない。私はシングルリターンポイントにより、コードをもっとテスト可能にしています。私はあなたのためのいくつかの参照を検索しようとします。 –

答えて

10

intを定義すると、デフォルトの初期化を起こさない宣言ではなく。変数の初期化は行われません。それはではない平均iはちょうどランダムな値を保持します。それは(既知の有効な)値を保持しています。したがって、あなたはまだそれを読むことができません。

コメントには、Angew経由のC++ 11標準からの関連引用があります。これは新しい制限ではなく、それ以来変更されていませんでした。

C++ 11 4.1/1、左辺値から右辺値への変換(基本的に変数の値を読み込み) glvalueが参照する...初期化されていない、この変換を必要とするプログラムは未定義の動作を持っています。

unitialised変数の任意の読み取りは、未定義の動作を引き起こし、そのが発生する可能性があります。 は振る舞いが定義されていないので、コンパイラは何かを行うことができます。この標準では、このような状況で何が起こるべきかについての要件はありません。

実際に言えば、最適化コンパイラは、UB上のどのようなコードでも単純に削除する可能性があります。何をすべきかについて正しい判断を下す方法はないので、何もしないことを決定することは完全に有効です(ちょうどサイズの最適化とスピードがあることもあります)。また、コメント欄に記されているように、コードを保持するかもしれませんが、iの読み込みを最も近い無関係な値で置き換えるか、異なるステートメントで異なる定数で置き換えてください。

変数の出力は「チェックそれはあなたが思うように、違いはありません。初期化されていない変数を 'チェック'してUBに対して自分自身を接種する方法はありません。変数を読み込む動作は、プログラムがすでに特定の値を書いている場合にのみ定義されます。

UBの特定の種類がなぜ発生するのか、私たちには何の示唆もありません。確定的に動作するようにコードを修正するだけです。

どうしてあなたは未初期化で使用しますか?これはちょうど「学術的」ですか?

+1

私は自分のコードを修正しましたが、タイプミスを発見したので、この質問は、コンパイラの中で何が起こっているのかをより良く知るのに役立ちます。私の理解は、intがある程度のスペースを占めることです。それを初期化せずに、それはランダムな迷惑を含むでしょうが、これはまだいくつかの整数を表すはずです。それは私が見た奇妙な行動を説明していません – CharlieB

+2

@CharlieB私の編集を参照してください。 UBは_precisely_ここで何が起こっているのかを説明しています。 –

+0

私の主な質問は、 "値を返し、すぐに同じ値を返すが、別の答えを得るprintステートメントを呼び出すにはどうすればいいですか?このコードは、 'i 'によって引き起こされたUBを解決した後でも、行ごとにスキップしているようです。 – CharlieB

-1

それあなたがint型の値を初期化していないときので、私はでなければならない、それは strlen(str) よりも大きいだろうガベージ値を保有しなければならないので、それはwhileループをスキップし、

while (i <= strlen(str)) 

これはエラーを出力し、-999を返します。非static保存期間のすべての基本タイプと同様に

+2

これは非現実的に楽観的です。 unitialised変数の使用はUBであるため、最適化コンパイラは、そのようなアクセスに依存するすべてのコードを削除することができます。そして、彼らは頻繁にします。 –

+0

@underscore_dここに初心者がいます。あなたはUBの略語で私を助けてくれますか? – MaNyYaCk

+2

未定義の動作。これはCおよびC++標準で使用されている重要な用語であり、それが表す概念は非常に危険で、少なくとも私はあなたの心の中で常にそれを学習することをお勧めします。 –

0

変数を初期化していないときは、メモリアドレスにあったものがランダムな値になりますので、while (i <=strlen(str))は予期せぬ動作をします。 常に初期化する必要があります。

(Visual Studioのデバッグ構成が自動的に変数を初期化する。)

+0

乾杯Matt、私は変数を初期化する必要があることを認識しています。これは本当にタイプミスでした。しかし、私はここで何が起こっているのかを理解しようとしています。確かに、比較は常に真または偽のいずれかを返すべきであり、whileループはそれに応じて動作します。 – CharlieB

+2

@CharlieBいいえ。コンパイラは、「私は決して初期化されていないことが分かります。だから、私はそれが最も便利だと考える価値があると考えることができます。 'i'を関数に渡すときにレジスタを変更せず、' i'を含むすべての条件をfalseとするなどです。 (注:コンパイラが実際にそれを行うかどうかはわかりませんが、あなたが見ている動作を説明します)。 – Angew

+1

ランダム値はありません。これは未定義の動作を呼び出します。 2つは基本的に異なり、同様の結果を出す義務はありません。乱数を必要とする人はstdlibの乱数ジェネレータクラスを使用します –

関連する問題