14

奇妙な問題に遭遇しました。整数変数を出力しようとしましたが、変数名を指定するのを忘れていました。

printf("%d");

それ以外の

printf("%d", integerName);

驚くべきことに、プログラムがコンパイルされ、出力があり、ランダムではありません。実際、それはたまたま最初に印刷したかったまさに整数であり、たまたま m-1 です。

エラーのあるprintfステートメントは、プログラムが実行され続ける限り、一貫して m-1 を出力します...つまり、ステートメントが読み取った場合とまったく同じように動作します

printf("%d", m-1);

この動作の背後にある理由を知っている人はいますか? コマンドラインオプションなしで g++ を使用しています。

#include <iostream>
#define maxN 100
#define ON 1
#define OFF 0

using namespace std;

void clearArray(int* array, int n);
int fillArray(int* array, int m, int n);

int main()
{
    int n = -1, i, m;
    int array[maxN];
    int found;

    scanf("%d", &n);

    while(n!=0)
    {
        found=0;
        m = 1;
        while(found!=1)
        {
            if(m != 2 && m != 3 && m != 4 && m != 6 && m != 12)
            {
                clearArray(array, n);
                if(fillArray(array, m, n) == 0)
                {
                    found = 1;
                }
            }
            m++;
        }

        printf("%d\n");

        scanf("%d", &n);
    }

    return 0;
}

void clearArray(int* array, int n)
{
    for(int i = 1; i <= n; i++)
        array[i] = ON;
}

int fillArray(int* array, int m, int n)
{
    int i = 1, j, offCounter = 0, incrementCounter;

    while(offCounter != n)
    {
        if(*(array+i)==ON) 
        {
            *(array+i) = OFF;
            offCounter++;       
        }
        else 
        {
            j = 0;
            while((*array+i+j)==OFF)
            {
                j++;
            }
            *(array+i+j) = OFF;
            offCounter++;           
        }
        if(*(array+13) == OFF && offCounter != n) return 1;
        if(offCounter ==n) break;

        incrementCounter = 0;       
        while(incrementCounter != m)
        {
            i++;
            if(i > n) i = 1;
            if(*(array+i) == ON) incrementCounter++; 
        }       
    }

    return 0;
}
4

6 に答える 6

27

あなたは「驚くべきことにプログラムがコンパイルされる」と言います。実際、それはまったく驚くべきことではありません。C & C++ では、関数が可変引数リストを持つことができます。printf の定義は次のようになります。

int printf(char*, ...);

「...」は、関数に 0 個以上のオプションの引数があることを示します。実際、C にオプションの引数がある主な理由の 1 つは、関数の printf & scanf ファミリをサポートするためです。

C には、printf 関数に関する特別な知識はありません。あなたの例では:

printf("%d");

コンパイラは、書式文字列を分析せず、整数引数が欠落していると判断しません。これは完全に正当な C コードです。引数が欠落しているという事実は、実行時にのみ現れるセマンティックの問題です。printf 関数は、引数が指定されていると想定し、スタックでそれを探します。そこにあるものは何でも拾います。たまたまあなたの特別なケースでは正しいものが印刷されていますが、これは例外です。一般に、ガベージ データが取得されます。この動作はコンパイラごとに異なり、使用するコンパイル オプションによっても異なります。コンパイラの最適化をオンにすると、異なる結果が得られる可能性があります。

私の回答に対するコメントの 1 つで指摘されているように、一部のコンパイラには、誤った printf/scanf 呼び出しを実際に検出できる「リント」のような機能があります。これには、コンパイラがフォーマット文字列を解析し、予想される追加の引数の数を決定することが含まれます。これは非常に特殊なコンパイラの動作であり、一般的なケースではエラーを検出しません。つまり、printf と同じシグネチャを持つ独自の「printf_better」関数を作成すると、コンパイラは引数が欠落しているかどうかを検出しません。

于 2009-01-13T03:13:24.297 に答える
10

どうなるかはこんな感じ。

printf("%d", m);

ほとんどのシステムでは、文字列のアドレスがスタックにプッシュされ、次に'm'整数としてプッシュされます (int/short/char であると仮定します)。printfは基本的に - として宣言されているため、警告はありません'int printf(const char *, ...);'。「何でもあり」を意味します。

したがって、「なんでもあり」なので、そこに変数を入れると奇妙なことが起こります。int より小さい整数型はすべて int になります。何も送らなくてもOKです。

printf 実装 (または少なくとも「単純な」実装) では、va_listandの使用法を見つけることができますva_arg(名前は、適合性に基づいて若干異なる場合があります)。これらは、実装が引数リストの「...」部分を歩き回るために使用するものです。ここでの問題は、型チェックがないことです。型チェックがないため、フォーマット文字列を見て、次があるはずだと判断したprintfときに、実行スタックからランダム データをプルします。("%d")'int'

暗闇の中でのランダムショットは、printf の直前に行った関数呼び出しが'm-1'2 番目のパラメーターとして渡された可能性があると言うでしょうか? これは多くの可能性のうちの 1 つですが、たまたまそうなったら興味深いことです。:)

幸運を。

ところで、最近のほとんどのコンパイラ (GCC だと思いますか?) には、この問題を検出するために有効にできる警告があります。リントも同様だと思います。残念ながら、VC では、無料で取得する代わりに /analyze フラグを使用する必要があると思います。

于 2009-01-13T03:05:31.770 に答える
4

スタックからintを取得しました。

http://en.wikipedia.org/wiki/X86_calling_conventions

于 2009-01-13T03:06:14.017 に答える
1

あなたはスタックを覗き込んでいます。オプティマイザーの値を変更すると、これが変わる可能性があります。変数の宣言の順序を変更します (特に) m。レジスタm変数を作成します。グローバルm変数を作成します。

何が起こるかには、いくつかのバリエーションがあります。

これは、単純な I/O を行うときに発生する有名なバッファ オーバーラン ハックに似ています。

于 2009-01-13T03:07:21.970 に答える
0

1 つの動作が見つかりました。無効なメモリ アクセスなど、他の動作である可能性があります。

于 2009-01-13T02:58:51.410 に答える