1

次のような小さなプログラムを作成しました。

void foo () {
  char *str = "+++"; // length of str = 3 bytes
  char buffer[1];

  strcpy (buffer, str);

  cout << buffer;
}

int main () {
  foo ();
}

バッファのサイズが str よりも小さいため、スタック オーバーフロー例外が発生することを期待していましたが、+++ が正常に出力されました... 誰かがなぜこれが起こったのか説明できますか?
どうもありがとうございました。

4

6 に答える 6

13

未定義動作(UB)が発生し、運が悪かったのですが、クラッシュしませんでした。
割り当てられたメモリの範囲を超えて書き込むことは未定義動作であり、UBはクラッシュを保証しません。何かが起こる可能性があります。
未定義の動作とは、動作を定義できないことを意味します。

于 2013-01-03T10:50:19.480 に答える
2

未定義の動作...

この場合、「正しい」結果が得られる可能性が高い理由を実際に気にする場合は、いくつかの要因があります。ストレージクラスを持つ変数auto(つまり、通常のローカル変数)は、通常、スタックに割り当てられます。通常の場合、スタック上のすべてのアイテムは特定のサイズの倍数になります。intたとえば、通常の32ビットシステムでは、スタックに割り当てることができる最小のアイテムは32ビットになります。言い換えると、通常の32ビットシステムでは、4バイトのスペースがあります(charその用語が必要な場合は、4バイトのうち)。

たまたま、ソース文字列には3文字と、合計4文字のNULターミネータしか含まれていませんでした。偶然にbufferも、割り当てを少なくするように指示したにもかかわらず、コンパイラが(ある種の)割り当てを強制されたスペースに収まるほど短いことがありました。

ただし、より長い文字列をターゲットにコピーした場合(おそらく、1バイト/文字だけ長くなる可能性があります)、大きな問題が発生する可能性が大幅に高くなります(64ビットソフトウェアでは、おそらくさらに長くする必要があります)。

考慮すべきもう1つのポイントがあります。システムとスタックの成長方向によっては、割り当てたスペースの最後をうまく書き込むことができ、それでも機能しているように見える場合があります。で割り当てbufferましたmain。で定義されている他の唯一のものmainはですがstr、これは単なる文字列リテラルです。したがって、文字列リテラルのアドレスを格納するためのスペースが実際に割り当てられていない可能性があります。最終的に、文字列リテラル自体が静的に割り当てられ(スタック上ではなく)、そのアドレスが使用した場所に置き換えられますstr。したがって、あなたがの終わりを過ぎて書くならばbuffer、スタックの一番上に残っているスペースに書き込んでいるだけかもしれません。通常の場合、スタックは一度に1ページずつ割り当てられます。ほとんどのシステムでは、ページのサイズは4Kまたは8Kであるため、スタックで使用されるスペースの量がランダムである場合、それぞれ平均2Kまたは4Kの空き容量が期待できます。

実際には、これが入っmainていて他に何も呼び出されていないため、スタックはほとんど空であると予想できます。したがって、スタックの一番上に未使用のスペースが1ページ近くある可能性があります。そのため、文字列をにコピーします。宛先は、ソース文字列が非常に長くなるまで(たとえば、数キロバイト)、機能しているように見える場合があります。

ただし、それよりもはるかに早く失敗することが多い理由については、通常、スタックは下向きに成長しますが、によって使用されるアドレスbuffer[n]は上向きに成長します。通常の場合、スタックの「上」の次の項目は、呼び出し元のスタートアップコードへbufferのリターンアドレスになります。したがって、スタックのスペースの量を超えて書き込むとすぐに(上記のように、指定したサイズよりも大きくなる可能性があります)からの差出人アドレスを上書きすることになります。その場合、内部のコードは正常に機能しているように見えることがよくありますが、実行(試行)がメインから戻るとすぐに、書き込んだデータをリターンアドレスとして使用することになり、その時点であなたは目に見える問題が発生する可能性がはるかに高くなります。mainmainbuffermainmain

于 2013-01-03T11:22:29.977 に答える
2

スタックオーバーフローは未定義の動作であるため発生しません。つまり、何かが発生する可能性があります。

今日の多くのコンパイラには、スタックの問題をチェックするためのコードを挿入するように指示する特別なフラグがありますが、多くの場合、コンパイラにそれを有効にするように明示的に指示する必要があります。

于 2013-01-03T10:51:48.743 に答える
1

何が起こるかを概説します:

運が良ければすぐにクラッシュします。または、技術的に定義されていないため、他の何かが使用しているメモリアドレスに書き込むことになる可能性があります。buffer[1]1つと1つの2つのバッファがあり、のメモリアドレスがと同じlongbuffer[100]である可能性があると仮定します。これは、で終了することを意味します(nullで終了するため)。buffer[2]longbuffer[0]long bufferlongbuffer[1]

char *s = "+++";
char longbuffer[100] = "lorem ipsum dolor sith ameth";
char buffer[1];

strcpy (buffer, str);

/*
buffer[0] = +
buffer[1] = +
buffer[2] = longbuffer[0] = +
buffer[3] = longbuffer[0] = \0 <- since assigning s will null terminate (i.e. add a \0)
*/

std::cout << longbuffer; // will output: +

明確にするのに役立つことを願って、これらのメモリアドレスがランダムな場合に同じになる可能性は非常に低いことに注意してください、しかしそれは起こる可能性があり、同じタイプである必要はありません。割り当てによって上書きされます。次に、(現在は破棄されている)変数を使用しようとすると、クラッシュする可能性があります。クラッシュは実際の問題とはあまり関係がないように見えるため、デバッグ時に少し面倒になります。(つまり、スタック上の変数にアクセスしようとするとクラッシュしますが、実際の問題は、コード内の別の場所で変数が破壊されたことです)。buffer[2]buffer[3]

于 2013-01-03T11:20:07.813 に答える
0

スタックの破損が発生しています。これは未定義の動作ですが、幸いなことにクラッシュは発生しませんでした。プログラムで以下の変更を行って実行すると、スタックの破損により確実にクラッシュします。

void foo () {
  char *str = "+++"; // length of str = 3 bytes
  int a = 10;
  int *p = NULL;
  char buffer[1];
  int *q = NULL;
  int b = 20;

  p = &a;
  q = &b;

  cout << *p;
  cout << *q;

  //strcpy (buffer, str);

  //Now uncomment the strcpy it will surely crash in any one of the below cout statment.
  cout << *p;
  cout << *q;

  cout << buffer;
}
于 2013-01-03T11:25:22.087 に答える
0

明示的な境界チェックや例外strcpyのスローはありません。これは C 関数です。C++ で C 関数を使用する場合は、境界などをチェックする責任を負うか、使用に切り替える必要がありstd::stringます。

この場合はうまくいきましたが、重要なシステムでは、このアプローチを採用すると、単体テストは成功するかもしれませんが、本番環境ではコード バーフが発生する可能性があります。これは望ましくない状況です。

于 2013-01-03T10:54:29.613 に答える