2010-11-30 1 views
8

それはなぜ機能しますか?未割り当てメモリをエラーなく使用していますか?

#include <iostream> 
using namespace std; 

int main() { 
    float* tab[3]; 

    int i = 0; 
    while(i < 3) { 
     tab[i] = new float[3-i]; 
     i++; 
    } 

    cout << tab[2][7] << endl; 
    tab[2][7] = 6.87; 
    cout << tab[2][7] << endl; 

    i = 0; 
    while(i < 3) 
     delete[] tab[i]; 
} 

この1つはありませんか?

#include <iostream> 
using namespace std; 

int main() { 
    float* tab = new float[3]; 

    cout << tab[7] << endl; 
    tab[7] = 6.87; 
    cout << tab[7] << endl; 

    delete[] tab; 
} 

私は両方のプログラムをWin XPでMS VS 2008と一緒に試しましたが、両方ともエラーなしでコンパイルされ、最初のプログラムはエラーなしで実行されました。 2番目のポップアップウィンドウにエラーウィンドウが表示されましたが、私はそれを思い出すことができず、再現できません(現時点ではWindowsへのアクセス権はありません)。

g ++でLinux(Kubuntu 10.10とプリコンパイル済みカーネルパッケージバージョン2.6.35.23.25)を試してみましたが、コンパイルと実行の両方にエラーは発生しません。

なぜですか? 「未割り当てメモリへの不正アクセス」のようなポップアップはありませんか?

私はそれがエラーなしでコンパイルされるべきだと知っていますが、私はそれがなければ実行してはいけないと思っていました...そして、なぜ2番目の例がWindowsではなくLinuxでエラーになるのですか?

答えて

9

未割り当てメモリを使用すると、未定義の動作が発生します。同じシステムとコンパイラでも、ハードウェアとコンパイラのさまざまな組み合わせにかかわらず、これを実行しても何が起こるかは期待できません。

プログラムがすぐにクラッシュすることがあります。しばらく動作してから後で失敗する可能性があります。完全に動作するように見えます。

あなたが所有していないメモリにアクセスすることは、常にプログラミングエラーです。 「時にはうまくいく」という正しい操作の外観を考えてはいけません。「私は本当に不運になり、私のバグはすぐには表示されません」と考えてください。

5

どちらも境界外配列アクセスを行います.3つの浮動小数点ポインタの配列を持ち、8番目の配列にアクセスしています。これはクラッシュするように拘束されています。

しかし、Javaや他の管理対象言語とは異なり、(それのパフォーマンスコストは高くないため)各配列アクセスの明示的な境界チェックはありません。だからあなたが持っている唯一の境界はあなたのMMUです。アプリケーションに属していないメモリにアクセスすると、クラッシュします。割り当てられていないメモリにヒットしたにもかかわらず、プロセスの一部になってしまった場合(ガードワードなど)、それはうっかり成功します。これは追跡が非常に難しいバグのための素晴らしいレシピです。

境界チェックが重要です。できるときはいつでもやりなさい。

1

最初の例では、タブ[2]の値は有効なメモリを指しています。タブ[2] +7は割り当てられていませんが、それは可能です。 seg-faultはありません。

2番目のタブ[7]には値がありません。ランダムなビットです(おそらく0または0xDEADBEEFまたは最後に値があったとしても)。これは、このアプリケーションがアクセスするのに有効なメモリを指しているとは限りません。従って:ブーム。

0

メモリアクセス保護はあまり細かくはありません。いくつかのメモリを割り当てると、プログラムに割り当てられた1ページ分のメモリが得られます。余分なメモリにアクセスしようとすると成功する可能性がありますが、あなたのプログラムに割り当てられている他のメモリを使いこなす可能性もあります。

これは、バッファオーバーランが攻撃として機能する理由です。多くの場合、アレイが使用された後に余分なメモリがどのように予測されるのでしょうか。あなたがそこに置くものを制御できるなら、上書きしたくないデータを上書きすることができます。コールスタックを上書きすることができれば、プロセスコンテキストで必要なコードを実行できます。これが管理ユーザーとして実行されているサービスの場合は、ローカル権限昇格があります。これが何らかのインターネットに面したサービスであれば、私はリモート実行攻撃を受けます。

あなたは、配列を使用する特定の目的を持っていない限り、std :: vectorのようなより強固な構造で作業することをお勧めします。 (そして、それでもあなたはget away with vectorsになる可能性があります)。

+0

でも、std :: vectorはデバッグビルドの境界チェックをしています(ありがたいことに!) –

+0

'vector :: at()'を使わないか、またはインデックスではなくイテレータで作業します。 – Eclipse

+0

実際、これもかなり間違っています。最初に、標準に「ページ」というようなものはなく、すべての実装があなたがその点でそうすると主張しているように動作するわけではありません。第2に、アレイがスタック上にある場合にのみスタックを上書きすることができます(スタックを使用するシステムでも)。フリーストアから割り当てられたメモリはスタックにありません。あなたが話している攻撃の種類は、 'void f(){char buf [SZ]; gets(buf); } 'はOPの問題ではありません。 –

7

マークの場合を除いて、他の答えは間違っていませんが、まったく正しいわけではありません。あなたのプログラムで明示的に割り当てたものの終わりを過ぎてデータにアクセスすることによって何がしているのかは、未定義の動作を引き起こしています。それは "仕事"を含む何でもすることができます。

私がこれを書き始めたときにスティーブの答えは存在しませんでした。

+2

+1の同時投稿でも定義されていない動作が発生する –

+0

私の問題は何ですか? –

関連する問題