2016-04-07 16 views
3

クラスについては、未定義の動作をgotoで示す必要がありました。私は、以下のプログラムを思い付いた:私は、コンパイラ(gccバージョン4.9.2)は、未定義の動作であることiへのアクセスについて私に警告することを期待する定義されていない動作が一貫していません

#include <stdio.h> 

int main() 
{ 
     goto x; 

     for(int i = 0; i < 10; i++) 
       x: printf("%d\n", i); 

     return 0; 
} 

ではなくさえして、警告はありません。

gcc -std=c99 -Wall -Wextra -pedantic -O0 test.c 

プログラムを実行すると、iは明らかにゼロに初期化されます。何が起こっているのかを理解するために、私は第二の可変jでコードを拡張:

#include <stdio.h> 

int main() 
{ 
    goto x; 

    for(int i = 0, j = 1; i < 10; i++) 
      x: printf("%d %d\n", i, j); 

    return 0; 
} 

今コンパイラが、私はそれが初期化されずにjにアクセスしていますことを私に警告します。私はそれを理解しますが、なぜiも初期化されていませんか?

+2

これは、定義されていない動作のものです。*未定義です*本当に何かが起こる可能性があります。 :)しかし、これは実行時に定義されている動作についてよりもコンパイラに関するものです。 –

+0

@JoachimPileborg:はい、しかし、なぜコンパイラはUBを 'j'で認識しますが、' i'では認識しませんか?それらは宣言され、まったく同じ方法でアクセスされます。 –

+1

私は '-O1'、' -O2'、 '-O3'を使ったとき両方のプログラムで' i'の警告を受け取りますが、 '-O0'を使うときや' -Ox'を使わないときは警告しません。 –

答えて

2

未定義の動作はランタイム現象です。したがって、コンパイラがそれを検出できることはまれです。定義されていない動作のほとんどのケースは、コンパイラの範囲外のことを行うときに呼び出されます。

さらに複雑にするために、コンパイラはコードを最適化するかもしれません。 iをCPUレジスタに入れて、スタックにjを入れることにしたり、その逆にすることを決めたとします。そして、デバッグビルド中に、すべてのスタックの内容をゼロに設定するとします。

定義されていない動作を確実に検出する必要がある場合は、コンパイラによる処理を超えたチェックを行う静的解析ツールが必要です。

+2

私は、最初の文章は誤解を招くものであると考えています。OPは特に翻訳段階での動作を求めていると考えています。 – user694733

+0

これはそれです。変数を明示的に 'register int'と宣言すると、両方に対して警告が表示されます。 –

+0

@ user694733 UBは約実行中です。コンパイラは、何が実行されるのかを調べるだけで、実行時に何かがUBになる可能性があることをヒントします。 – larkey

2

コンパイラは、 を初期化せずにjにアクセスしていることを警告します。私はそれを理解しますが、なぜ私は として初期化されていませんか?

これは未定義の動作の点ですが、時には動作しない、または動作しない、または部分的に、またはゴミを印刷します。問題は、コンパイラがコンパイル時に何をしているのかを正確に知ることができないということです。コンパイラが一貫性のない結果を生成するのではなく、自分自身を認めるとき、その動作は未定義です。

この時点で保証されるのは、これがどのように再生されるかについては何も保証されていないということだけです。コンパイラごとに異なる結果が得られたり、最適化レベルが異なる場合があります。

コンパイラはこれをチェックする必要もなく、これを処理する必要もないため、コンパイラはこれを実行しません。コンパイラを使用して、未定義の動作を確実に確認することはできません。それは何の単位テストと多くのテストケースや統計解析が対象です。

2

変数の初期化をスキップするために "goto"を使用すると、コンパイラーは、一貫して動作しない可能性がありますが、何も持たない不確定値をもたらすプラットフォームでも、その他の副作用。この場合のgccの振る舞いは、例えばgccの場合の挙動と同程度には見えない。整数オーバーフローが発生しますが、最適化はやや面白いかもしれません。所与:

int test(int x) 
{ 
    int y; 
    if (x) goto SKIP; 
    y=x+1; 
    SKIP: 
    return y*2; 
} 

int test2(unsigned short y) 
{ 
    int q=0;int i; 
    for (i=0; i<=y; i++) 
    q+=test(i); 
    return q; 
} 

コンパイラは、すべての定義された場合には、テスト2を返すことを観察し、このようにTEST2と同等のコードを生成することによって、ループを排除することができる:ただし、

int test2(unsigned short y) 
{ 
    return (int)y << 1; 
} 

このような例をコンパイラがUBを良質に扱っているという印象を与えるかもしれません。残念なことに、gccの場合、それはもはや一般的に真実ではありません。以前は、ハードウェアトラップのないマシンでは、不確定値の使用を、一貫性のある方法で動作するかもしれないし、動作しないかもしれないが、他の副作用を伴わない任意の値を単に出力するものとして取り扱っていた。変数初期化をスキップするためにgotoを使用する場合はですが、は変数に意味のない値を持つ以外の副作用を引き起こしますが、gccの作成者はそれを悪用することを決して意味しません将来の自由。

+0

私の最初の質問(私が投稿した特定のコードについて)と何が関係しているのか分かりません。これは、UBとはまったく異なっているようです。 –

+0

@AndreasUnterweger:申し訳ありません - gccの治療について "驚くべきこと"は何も見つからないと思ったので、あなたの混乱の点は "未定義の振る舞い"のコンセプトと思っていました。良い例が必要で機械コードの何かを理解したい場合は、コードのわずかな変更が出力にどのように影響するかを素早く確認できるので、godbolt.orgのサイトはすばらしいです。 – supercat

+0

@AndreasUnterweger:クイックテストでは、gccの「goto」最適化に関する警告は表示されませんが、それが賢明な方法がいくつか示されています。上記のコードをデモするために更新します。 – supercat

関連する問題