6

以下のプログラムを検討してください

    char str[5];
    strcpy(str,"Hello12345678");
    printf("%s",str);

このプログラムを実行すると、セグメンテーション違反が発生します。

しかし、strcpy を次のように置き換えると、プログラムは正常に実行されます。

strcpy(str,"Hello1234567");

したがって、5 文字を超える長さの他の文字列を str にコピーしようとすると、クラッシュするはずです。

では、なぜ「Hello1234567」ではクラッシュせず、「Hello12345678」、つまり長さが 13 または 13 を超える文字列でのみクラッシュするのでしょうか。

このプログラムは 32 ビット マシンで実行されました。

4

8 に答える 8

31

注目すべき標準動作には 3 つのタイプがあります。

1/定義された動作。これは、準拠しているすべての実装で機能します。これはご自由にお使いください。

2/実装定義の動作。述べたように、それは実装に依存しますが、少なくともまだ定義されています。実装は、これらの場合に何を行うかを文書化する必要があります。移植性を気にしない場合は、これを使用してください。

3/未定義の動作。何でも起れる。そして、あなたのコンピューター全体が裸の特異点に崩壊し、それ自体、あなたとあなたの同僚の大部分を飲み込むことを含む、あらゆることを意味します. これは絶対に使用しないでください。これまで!真剣に!私をあそこに来させないでください。

4 文字を超える文字と 0 バイトを a にコピーすると、char[5]未定義の動作になります。

真剣に、なぜあなたのプログラムが 14 文字ではなく 13 文字でクラッシュするのかは問題ではありません。ほぼ確実に、スタック上のクラッシュしない情報を上書きしているので、いずれにせよ、プログラムは間違った結果を生成する可能性が高いでしょう。実際、少なくともクラッシュの可能性がある悪影響に頼る必要がなくなるため、クラッシュの方が優れています。

配列のサイズをより適切なもの (char[14]この場合は利用可能な情報) に増やすか、対応できる他のデータ構造を使用してください。


アップデート:

余分な 7 文字では問題が発生しないのに 8 文字では問題が発生する理由を知りたがっているように見えるので、 を入力したときに考えられるスタック レイアウトを想像してみましょうmain()。実際のレイアウトはコンパイラが使用する呼び出し規約に依存するため、「可能」と言います。C スタートアップ コードは と を使用して呼び出しmain()argcいるargvため、 の開始時のスタックは、main()にスペースを割り当てた後、次のchar[5]ようになります。

+------------------------------------+
| C start-up code return address (4) |
| argc (4)                           |
| argv (4)                           |
| x = char[5] (5)                    |
+------------------------------------+

次のようにバイトHello1234567\0を書き込む場合:

strcpy (x, "Hello1234567");

に、 and をx上書きしますが、 から戻ると、それで問題ありません。具体的には、移入、移入、および移入します。実際に使用しようとしない限り、および/またはその後は問題ありません。argcargvmain()Hellox1234argv567\0argc argcargv

+------------------------------------+ Overwrites with:
| C start-up code return address (4) |
| argc (4)                           |   '567<NUL>'
| argv (4)                           |   '1234'
| x = char[5] (5)                    |   'Hello'
+------------------------------------+

ただし、 に書き込むとHello12345678\0(余分な "8" に注意してください) 、とおよび戻りアドレスの 1 バイトがx上書きされるため、C スタートアップ コードに戻ろうとすると、代わりに妖精の国に移動します。argcargv main()

+------------------------------------+ Overwrites with:
| C start-up code return address (4) |   '<NUL>'
| argc (4)                           |   '5678'
| argv (4)                           |   '1234'
| x = char[5] (5)                    |   'Hello'
+------------------------------------+

繰り返しますが、これはコンパイラの呼び出し規約に完全に依存します。別のコンパイラが常に配列を 4 バイトの倍数にパディングし、別の 3 文字を書き込むまでコードが失敗しない可能性があります。同じコンパイラでも、アライメントが確実に満たされるように、スタック フレームに異なる方法で変数を割り当てる場合があります。

それが未定義の意味です。何が起こるかわかりません。

于 2009-04-05T07:14:39.220 に答える
7

スタックにコピーしているため、プログラムをクラッシュさせるために必要な追加データの量は、コンパイラがスタックに置いたものに依存します。

一部のコンパイラは、バッファ サイズを 1 バイト超えるだけでクラッシュするコードを生成する場合があります。動作は未定義です。

サイズ 13 は、関数が返されたときにクラッシュする戻りアドレスなどを上書きするのに十分だと思います。ただし、別のコンパイラまたは別のプラットフォームでは、異なる長さでクラッシュする可能性があります。

また、プログラムが長時間実行された場合、重要度の低いものが上書きされた場合、プログラムが異なる長さでクラッシュする可能性があります。

于 2009-04-05T07:04:43.797 に答える
5

32 ビット Intel プラットフォームの場合の説明は次のとおりです。スタックで char[5] を宣言すると、アラインメントのために、コンパイラは実際には 8 バイトを割り当てます。次に、関数が次のプロローグを持つのが一般的です。

push ebp
mov ebp, esp

これにより、ebp レジストリ値がスタックに保存され、esp 値を使用してパラメーターにアクセスするために、esp レジスター値が ebp に移動されます。これにより、スタック上でさらに 4 バイトが ebp 値で占有されます。

エピローグで ebp が復元されますが、その値は通常、スタックに割り当てられた関数パラメーターへのアクセスにのみ使用されるため、ほとんどの場合、上書きしても問題はありません。

したがって、次のレイアウトがあります (Intel ではスタックが下向きに成長します): 配列用に 8 バイト、次に ebp 用に 4 バイト、通常は戻りアドレスです。

これが、プログラムをクラッシュさせるために少なくとも 13 バイトを上書きする必要がある理由です。

于 2009-04-05T08:00:31.277 に答える
2

上記の回答に追加するには、 Valgrindなどのツールを使用して、このようなバグをテストできます。Windows を使用している場合は、この SO スレッドを参照してください。

于 2009-04-05T07:24:55.310 に答える
1

「str」配列の後のスタックに何があるかによって異なります。その数の文字をコピーするまで、たまたま重要なものを踏みにじっていません。

そのため、関数内に他に何があるか、使用するコンパイラ、およびおそらくコンパイラ オプションにも依存します。

13 は 5 + 8 です。これは、str 配列の後に 2 つの重要でない単語があり、次に重要なもの (おそらく戻りアドレス) があることを示唆しています。

于 2009-04-05T07:05:36.543 に答える
1

それが未定義動作 (UB) の純粋な美しさです。それは未定義です。

あなたのコード:

char str[5];
strcpy(str,"Hello12345678");

str5 バイト/文字しか保持できない14 バイト/文字を書き込みます。これにより、UB が呼び出されます。

于 2009-04-05T07:05:56.103 に答える
0

動作が定義されていないためです。strncpy を使用します。詳細については、このページ http://en.wikipedia.org/wiki/Strcpy を参照してください。

strncpy は、ソース文字列の長さが >= n の場合、NULL 終端を追加しないため、安全ではありません。ここで、n は宛先文字列のサイズです。

char s[5];
strncpy(s,5,"test12345");
printf("%s",s); // crash

これを軽減するために、常に strlcpy を使用します。

于 2009-04-06T07:29:36.757 に答える
0

Q: では、なぜ「Hello1234567」ではクラッシュせず、「Hello12345678」、つまり長さが 13 または 13 を超える文字列の場合にのみクラッシュするのでしょうか。

  • 動作が定義されていないためです。strncpy を使用します。詳細については、このページhttp://en.wikipedia.org/wiki/Strcpyを参照してください。
于 2009-04-05T07:42:18.327 に答える