2011-06-23 4 views
14

以下のコードは@ runtimeがクラッシュすることなく動作しますか?アレイの最後を過ぎて書き込むと、プログラムがクラッシュしないのはなぜですか?

また、サイズは完全にマシン/プラットフォーム/コンパイラに依存しています!!私は64ビットマシンで最大200を与えることさえできます。メイン関数のセグメンテーションフォルトがOSでどのように検出されるのでしょうか?

void main(int argc, char* argv[]) 
{ 
    int arr[3]; 
    arr[4] = 99; 
} 

このバッファスペースはどこから来ていますか?これはプロセスに割り当てられたスタックですか?

+7

スタックオーバーフローは、スタックから多すぎるメモリが割り当てられた場合に発生します。この場合、 'sizeof(int)== 4 'と仮定すると、スタックから12バイトの小さなバイトを割り当てたことになります。あなたのコードは配列の最後を超えて書いています。スタックオーバーフローではありません。それは_振る舞い_です。 –

+0

RAMの残りの部分を持っているのと同じ場所から来ています。 'arr [3]'は "私の使用に利用可能なスペースの3" intを指定する "という意味で、" etherからスペースの3 "intを作る"という意味ではありません。物理的に可能です。 Davidが言うようにUBである「arr」に隣接するメモリ/アドレス(実際には隣にありますが、実際には)が何であっても、あなたはそれに書きしています。はい、それはあなたのスタックの一部です(CとC++の標準はスタックについては話しませんが、実際には自動変数がどこに行くのでしょうか)。 –

+0

@vprajan - 注意を引く良い答えがあるので、質問を反映するためにタイトルを更新しました。 –

答えて

67

何か...

は、次のCプログラムを検討:それをコンパイルし、それを実行した後

int q[200]; 

main(void) { 
    int i; 
    for(i=0;i<2000;i++) { 
     q[i]=i; 
    } 
} 

を、コア・ダンプが生成される:

$ gcc -ggdb3 segfault.c 
$ ulimit -c unlimited 
$ ./a.out 
Segmentation fault (core dumped) 

現在、死後分析を実行するためにGDBを使用:

$ gdb -q ./a.out core 
Program terminated with signal 11, Segmentation fault. 
[New process 7221] 
#0 0x080483b4 in main() at s.c:8 
8  q[i]=i; 
(gdb) p i 
$1 = 1008 
(gdb) 

割り当てられた200個のアイテムの外に書いたときにプログラムがセグメンテーションを実行しなかった私は1008、なぜそれがクラッシュした?

ページを入力してください。

#include <stdio.h> 
#include <unistd.h> // sysconf(3) 

int main(void) { 
    printf("The page size for this system is %ld bytes.\n", 
      sysconf(_SC_PAGESIZE)); 

    return 0; 
} 

出力できます::

をUNIX/Linux上のいくつかの方法でページサイズを決定することができ

一つは、一つの方法は、このようなシステム機能にsysconf()を使用することです

このシステムのページサイズは4096バイトです。

または1つは、コマンドラインユーティリティこのようなgetconfを使用することができます。

$ getconf PAGESIZE 
4096 

死後

それは、セグメンテーションフォルトがI = 200で私= 1008でない発生することが判明理由を見つけ出すことができます。いくつかの死後ananlysisを行うには、GDBを起動します。

$gdb -q ./a.out core 

Core was generated by `./a.out'. 
Program terminated with signal 11, Segmentation fault. 
[New process 4605] 
#0 0x080483b4 in main() at seg.c:6 
6   q[i]=i; 
(gdb) p i 
$1 = 1008 
(gdb) p &q 
$2 = (int (*)[200]) 0x804a040 
(gdb) p &q[199] 
$3 = (int *) 0x804a35c 

qはアドレス0x804a35c、あるいはむしろ、Qの最後のバイト[199]でで終了その場所にありました。ページサイズは以前の4096バイトと同じですが、マシンの32ビットワードサイズでは、仮想アドレスが20ビットのページ番号と12ビットのオフセットに分解されます。

Q []仮想ページ番号で終了:

0x804a = 32842 オフセット:

0x35c = 860 ように依然として存在した:

4096 - その上に残さ864 = 3232の バイトq []が割り当てられたメモリのページ。その空間は、保持することができる:

4分の3232 = 808 整数を、それが私たちのすべては、それらの要素が存在しないことを知っている

1008に位置200でのQの要素を含んでいたかのように、コードは、それを処理し、そして私たちはそのページへの書き込み権限を持っているので、コンパイラは文句を言いませんでした。 i = 1008でq []が書込み許可のない別のページのアドレスを参照したときにのみ、仮想メモリhwがこれを検出してセグメンテーションをトリガしました。

整数は4バイトで格納されます。つまり、このページには808(3236/4)個の追加の偽の要素が含まれています。これは、q [200]、q [201] seg故障をトリガすることなく、要素199 + 808 = 1007(q [1007]まで)まで増加する。 q [1008]にアクセスすると、アクセス許可が異なる新しいページが入力されます。

+4

+1と私は二度投票したかもしれないことを願って – SJuan76

+0

+1、優れた答え! – Nim

+0

+!..優秀な.......... –

5

あなたはあなたの配列の境界の外で書いているので、コードの動作は未定義です。

何かが起こる可能性のある未定義の動作の性質(segfaultの不足を含む)(コンパイラは境界チェックを実行する義務がありません)。

割り当てられていないメモリに書き込んでいますが、それが起こっている可能性があります。おそらく、他には使用されていません。コードの外見上無関係な部分、OS、コンパイラ、最適化フラグなどを変更すると、コードの動作が異なる場合があります。

つまり、一度その領域に入ると、すべてのベットはオフになります。

2

これは未定義の動作です。何も問題はありません。最も可能性の高い理由は、プログラムの動作が前に依存していないメモリ領域を上書きすることです。そのメモリは技術的に書き込み可能です(ほとんどの場合、スタックサイズは約1Mバイトです)。あなたはこれに頼るべきではありません。

0

コードには未定義の動作があります。それは、何でも何もできないことを意味します。コンパイラやOSなどによってはクラッシュする可能性があります。

多くの場合、ほとんどのコンパイラでコードはコンパイルされません

void mainがあるため、C標準とC++標準の両方にint mainが必要です。

void mainに満足している唯一のコンパイラについては、Microsoft ’、Visual C++です。

コンパイラの欠陥だが、マイクロソフトは例のドキュメントの多くとvoid mainを生成しても、コード生成ツールを持っているので、彼らはおそらくそれを修正することはありません。しかし、マイクロソフト固有のvoid mainを記述することは、標準のint mainよりも1文字多く入力することを考慮してください。だから、基準にはいかないの?

乾杯& HTH。私は教育目的のためにいつか前に書いた、

0

セグメンテーションフォルトは、プロセスが所有していないメモリ内のページを上書きしようとすると発生します。バッファーが終わって遠くを走らない限り、segフォールトを引き起こさないでしょう。

スタックは、アプリケーションが所有するメモリブロックのいずれかに配置されています。この例では、何か重要なものを上書きしていないと、あなたは幸運なことになりました。おそらく未使用のメモリをいくつか上書きしてしまいました。もう少し不運だったら、スタック上の別の関数のスタックフレームを上書きしたかもしれません。

3

C++がCから継承した配列型を使用すると、暗黙的に範囲チェックを行わないように尋ねられます。

あなたの代わりに

void main(int argc, char* argv[]) 
{  
    std::vector<int> arr(3); 

    arr.at(4) = 99; 
} 

これをしようとした場合、あなた例外がスローされます。

C++は、チェックされたインターフェイスとチェックされていないインターフェイスの両方を提供します。あなたが使いたいものを選択するのはあなた次第です。

1

なぜ「検出されない」の質問に答える:ほとんどのCコンパイラは、コンパイル時にポインタやメモリを使って何をしているのか分析していないので、コンパイル時に何か危険なことを書いたことを誰も知らない。実行時には、あなたのメモリ参照をベビーシッターする管理された管理された環境もないので、誰もあなたが許可されていないメモリを読むことを止めません。メモリはその時点で割り当てられているので(スタックはスタックから離れていないため)、OSにも問題はありません。

メモリにアクセスしているときに手持ちしたい場合は、JavaやCLIなどの管理環境が必要です。プログラム全体が別のプログラムによって管理されています。ローカル変数のバッファオーバーフローのクラッシュがいくつかの要因に依存する場合/正確に関して

4

  1. オーバーフロー変数のアクセスを含む関数が呼び出された時点ですでにスタック上のデータの量

におけるオーバーフロー変数/配列スタックは下方を成長させることに注意してくださいに書き込まれたデータの量。私。プロセスの実行は、スタックとして使用されるメモリのエンドに近いスタックポインタから開始されます。システムの初期化コードは、ある種の「スタートアップ情報」を作成時にプロセスに渡すことを決定し、しばしばスタック上で実行することがあるためです。

これは通常です.エラーモード - オーバーフローコードを含む関数から復帰するとクラッシュします。

合計スタックのバッファに書き込まれるデータの量が、以前に(呼び出し元/初期化コード/その他の変数によって)使用されていたスタックスペースの合計よりも大きい場合、メモリアクセス最初にスタックの先頭(先頭)を超えて実行されます。クラッシュするアドレスは、スタックの先頭を超えてメモリにアクセスしているため、何もマップされていないため、ページ境界を超えた直後になります(SIGSEGV)。

この合計が現在スタックの使用されている部分のサイズより小さい場合は、うまく動作し、の後にというクラッシュが発生します。あなたの関数から戻るときは、x86/x64ではtrueです)。これは、CPU命令retが実際にスタック(復帰アドレス)からワードを取り、そこで実行をリダイレクトするためです。予想されるコードの場所の代わりに、このアドレスに何らかのゴミが含まれていると、例外が発生し、プログラムが終了します。 main()開始は、それが間、様々な目的のために、スタック上のスペースを割り当てますとき

[ esp   ] <return addr to caller> (which exits/terminates process) 
[ esp + 4  ] argc 
[ esp + 8  ] argv 
[ esp + 12  ] envp <third arg to main() on UNIX - environment variables> 
[ ...   ] 
[ ...   ] <other things - like actual strings in argv[], envp[] 
[ END   ] PAGE_SIZE-aligned stack top - unmapped beyond 

main()が呼び出されると、スタックは(32ビットx86のUNIXプログラムに)次のようになります。これを説明するために

オーバーフローする配列をホストする他のものこれは、あなたがarr[2]を超えて道を幸せにアクセスできることを意味

[ esp   ] <current bottom end of stack> 
[ ...   ] <possibly local vars of main()> 
[ esp + X  ] arr[0] 
[ esp + X + 4 ] arr[1] 
[ esp + X + 8 ] arr[2] 
[ esp + X + 12 ] <possibly other local vars of main()> 
[ ...   ] <possibly other things (saved regs)> 

[ old esp  ] <return addr to caller> (which exits/terminates process) 
[ old esp + 4 ] argc 
[ old esp + 8 ] argv 
[ old esp + 12 ] envp <third arg to main() on UNIX - environment variables> 
[ ...   ] 
[ ...   ] <other things - like actual strings in argv[], envp[] 
[ END   ] PAGE_SIZE-aligned stack top - unmapped beyond 

:これはそれのように見えるようになります。バッファオーバーフローに起因するさまざまなクラッシュのテイスターのために

、これを試みる:

#include <stdlib.h> 
#include <stdio.h> 

int main(int argc, char **argv) 
{ 
    int i, arr[3]; 

    for (i = 0; i < atoi(argv[1]); i++) 
     arr[i] = i; 

    do { 
     printf("argv[%d] = %s\n", argc, argv[argc]); 
    } while (--argc); 

    return 0; 
} 

、あなたは少し(たとえば、10)ビットによってバッファをオーバーフローさせたときにクラッシュがものになるかどうか異なる見ますスタックの終わりを超えてオーバーフローした場合と比較して、さまざまな最適化レベルと異なるコンパイラで試してみてください。 (例:コンパイラがiまたはargcをスタックに入れたときにコードがそれを上書きすると、さまざまな場所、多分無限ループでさえクラッシュするだけでなく、すべて正しくargv[]を印刷するとは限らない)ループ)。

関連する問題