未定義の動作...
この場合、「正しい」結果が得られる可能性が高い理由を実際に気にする場合は、いくつかの要因があります。ストレージクラスを持つ変数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
のリターンアドレスになります。したがって、スタックのスペースの量を超えて書き込むとすぐに(上記のように、指定したサイズよりも大きくなる可能性があります)からの差出人アドレスを上書きすることになります。その場合、内部のコードは正常に機能しているように見えることがよくありますが、実行(試行)がメインから戻るとすぐに、書き込んだデータをリターンアドレスとして使用することになり、その時点であなたは目に見える問題が発生する可能性がはるかに高くなります。main
main
buffer
main
main