6

私が C を教えているとき、いくつかのルールの「説得力のある」部分を GCC に任せることがあります。たとえば、関数のローカル変数が呼び出し間で値を保持しているとは考えないでください。

GCC は常に学生にこれらの教訓を教え、ローカル変数にゴミを入れて、学生が何が起こっているのかを理解するのを助けてくれました。

さて、このコードは間違いなく私を苦しめています。

#include <stdio.h>

int test(int x)
{
        int y;
        if(!x)
                y=0;
        y++;
        printf("(y=%d, ", y);
        return y;
}

int main(void)
{
        int a, i;

        for(i=0; i<5; i++)
        {
                a=test(i);
                printf("a=%d), ", a);
        }
        printf("\n");
        return 0;
}

出力は次のとおりです。

(y=1, a=1), (y=2, a=2), (y=3, a=3), (y=4, a=4), (y=5, a=5),

しかし、次の行にコメントすると:

       /* printf("(y=%d, ", y); */

出力は次のようになります。

a=1), a=32720), a=32721), a=32722), a=32723),

スイッチを使用してコードをコンパイルしました-Wallが、初期化せずにローカル変数を使用しても警告は表示されません。

警告を発生させる、または少なくともガベージを明示的に表示する GCC スイッチはありますか? 最適化スイッチを試してみたところ、コード出力が次のようになったので役に立ちました。

$ gcc test.c -o test -Wall -Os
$ ./test 
(y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1),
$ gcc test.c -o test -Wall -Ofast
$ ./test 
(y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1),
$ gcc test.c -o test -Wall -O0
$ ./test 
(y=1, a=1), (y=2, a=2), (y=3, a=3), (y=4, a=4), (y=5, a=5),
$ gcc test.c -o test -Wall -O1
$ ./test 
(y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1),
$ gcc test.c -o test -Wall -O2
$ ./test 
(y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1),
$ gcc test.c -o test -Wall -O3
$ ./test 
(y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1),

しかし、すべての場合で y=1 は一種のトリックです。ローカル変数がゼロで初期化されるように標準が変更されましたか?

4

3 に答える 3

4

それが未定義の動作の問題です。それは「未定義」です。

したがって、結果のセットは、コンパイラ/設定/メモリ内のもの/割り込みの組み合わせに完全に依存します。

問題を実証するために、「期待する」ものを出力するいくつかの設定に遭遇する可能性がありますが、それはただの運です。

あなたが発見したことは実際にはもっと重要です - 障害モードの数はあなたが想像できるよりも広い (幸運なことに、ハードドライブを再フォーマットしたものはまだありません)、そして最も有害で危険なタイプの「未定義の動作」はここで、動作は実際には 99.99% の確率で「期待どおり」です。

それはあなたを得る 0.01% です。

于 2013-06-13T18:47:42.923 に答える
2

あなたのプログラムは未定義の動作を引き起こします。にゼロ以外の値を渡すとtest()yは初期化されません。 printf含まれているかどうかに関係なく、結果を信頼することはできません。

警告が必要な場合、clang は次のような警告を表示します-Wsometimes-uninitialized

example.c:6:12: warning: variable 'y' is used uninitialized whenever 'if'
      condition is false [-Wsometimes-uninitialized]
        if(!x)
           ^~
example.c:8:9: note: uninitialized use occurs here
        y++;
        ^
example.c:6:9: note: remove the 'if' if its condition is always true
        if(!x)
        ^~~~~~
example.c:5:14: note: initialize the variable 'y' to silence this warning
        int y;
             ^
              = 0
1 warning generated.

手元にあるいくつかの GCC バージョンでテストしましたが、どれも警告を生成しませんでした。

于 2013-06-13T18:35:02.117 に答える
2

このアイデアに対する別の可能なアプローチは、関数の呼び出しの間に別の関数を呼び出すことtestです。他の関数がスタック スペースを使用している場合、スタック値が変更される可能性があります。たとえば、おそらく次のような関数を追加します。

int changeStack( int x )
{
    int y2 = x + 100;
    return y2;
}

そして、それに呼び出しを追加します。

    for(i=0; i<10; i++)
    {
            a=test(i);
            printf("a=%d), ", a);
            changeStack( i );
    }

もちろん、最適化レベルにもよりますが、デフォルトの gcc compile ( gcc test.c) を使用して、それを行うように変更した後、次の結果が得られました。

(y=1, a=1), (y=101, a=101), (y=102, a=102), (y=103, a=103), (y=104, a=104), (y=105, a=105), (y=106, a=106), (y=107, a=107), (y=108, a=108), (y=109, a=109),
于 2013-06-13T18:45:34.607 に答える