web-dev-qa-db-ja.com

ポインターをインクリメントしてから削除すると、プログラムがクラッシュするのはなぜですか?

ポインタについて大きな誤解があることに気付いたとき、プログラミングの練習をしていました。このコードがC++でクラッシュを引き起こす理由を誰かが説明してください。

#include <iostream>

int main()
{
    int* someInts = new int[5];

    someInts[0] = 1; 
    someInts[1] = 1;

    std::cout << *someInts;
    someInts++; //This line causes program to crash 

    delete[] someInts;
    return 0;
}

P.Sここで「新規」を使用する理由はないことを認識しています。例をできるだけ小さくしています。

30
Beetroot

プログラムをクラッシュさせるのは、実際にプログラムをクラッシュさせるとマークしたものの後のステートメントです!

mustdelete[]から戻るときと同じポインタをnew[]に渡します。

それ以外の場合、プログラムの動作はndefinedです。

80
Bathsheba

問題は、someInts++;を使用して、配列の2番目の要素のアドレスをdelete[]ステートメントに渡すことです。最初の(元の)要素のアドレスを渡す必要があります。

int* someInts = new int[5];
int* originalInts = someInts; // points to the first element
someInts[0] = 1;
someInts[1] = 1;

std::cout << *someInts;
someInts++; // points at the second element now

delete[] originalInts;
33
user1593881

ここで特定の実装の詳細に入ることなく、クラッシュの背後にある直感的な理由は、 delete[] が何をするかを考えることで簡単に説明できます。

new[]- expressionによって作成された配列を破棄します

delete[]に配列へのポインターを与えます。とりわけ、その配列の内容を保持するために割り当てたメモリを解放する必要があります。

アロケーターはどのように解放すべきかを知るのですか?与えられたポインタをキーとして使用して、割り当てられたブロックの簿記情報を含むデータ構造を検索します。どこかに、以前に割り当てられたブロックへのポインタと関連するブックキーピング操作との間のマッピングを格納する構造があります。

delete []に渡すポインターが対応するnew[]によって返されたものではない場合、このルックアップが何らかのフレンドリーなエラーメッセージになるようにしたい場合がありますが、それを保証する標準はありません。

したがって、new[]delete[]によって以前に割り当てられていなかったポインターが与えられた場合、実際には一貫した簿記構造ではない何かを見ることになります。ワイヤーが交差します。クラッシュが発生します。

または、delete[]が「ねえ、このポインタは前に割り当てた領域内のどこかを指しているように見えます。戻って、その領域を割り当てたときに返されたポインタを見つけて使用します」簿記情報を検索する」が、再び、標準にはそのような要件はありません。

2番目の(配列)形式の場合、expressionはnullポインター値、または配列式のnew-expressionで以前に取得したポインター値である必要があります。 ifexpressionがelseの場合、配列のない形式のnew -expression、動作は未定義です。 [強調鉱山]

この場合、すぐに何か間違ったことをしたことがわかったので、あなたは幸運です。

PS:これは手で波打った説明です

19
Sinan Ünür

ブロック内のポインターをインクリメントし、そのインクリメントされたポインターを使用してブロックのさまざまな部分にアクセスできます。

ただし、Newから取得したポインターをDeleteに渡す必要があります。インクリメントされたバージョンではなく、他の手段で割り当てられたポインターでもありません。

どうして?まあ、警官の答えは、それが標準が言うことだからです。

実用的な答えは、メモリのブロックを解放するには、メモリマネージャがブロックに関する情報を必要とするためです。たとえば、開始場所と終了場所、隣接するチャンクが空いているか(通常、メモリマネージャーは隣接する空きチャンクを結合します)、所属するアリーナ(マルチスレッドメモリマネージャーでのロックにとって重要)などです。

この情報は通常、割り当てられたメモリの直前に保存されます。メモリマネージャーは、ポインターから固定値を減算し、その場所で割り当てメタデータの構造を探します。

割り当てられたメモリのブロックの開始点を指していないポインタを渡すと、メモリマネージャは減算を実行してその制御ブロックを読み取ろうとしますが、読み取りが最終的に有効な制御ブロックではありません。

運がよければコードはすぐにクラッシュしますが、運が悪ければ微妙なメモリ破損が発生します。

8
plugwash