voidポインターを削除しても安全ですか?


96

次のコードがあるとします。

void* my_alloc (size_t size)
{
   return new char [size];
}

void my_free (void* ptr)
{
   delete [] ptr;
}

これは安全ですか?または削除ptrするchar*前にキャストする必要がありますか?


なぜ自分でメモリ管理をしているのですか?どのデータ構造を作成していますか?C ++では、明示的なメモリ管理を行う必要はほとんどありません。通常は、STL(またはピンチでBoost)から処理するクラスを使用する必要があります。
Brian

6
読む人のためだけに、私はwin c ++で私のスレッドのパラメーターとしてvoid *変数を使用しています(_beginthreadexを参照)。通常、それらは実際にはクラスを指しています。
ryansstack 2009年

3
この場合、それはnew / deleteの汎用ラッパーであり、割り当て追跡統計または最適化されたメモリプールを含むことができます。他の場合では、オブジェクトポインターがvoid *メンバー変数として誤って格納され、適切なオブジェクト型にキャストバックせずにデストラクタで誤って削除されたことがあります。だから私は安全性/落とし穴に興味がありました。
An̲̳̳drew 2009年

1
new / deleteの汎用ラッパーの場合、new / deleteオペレーターをオーバーロードできます。使用する環境に応じて、メモリ管理にフックを割り当てて割り当てを追跡します。何を削除するのかわからない状況になった場合は、デザインが最適ではなく、リファクタリングが必要であることを強く示唆します。
Hans

38
質問に答えるのではなく、質問することが多すぎると思います。(ここだけでなく、すべてのSOでも)
ペトルザ

回答:


24

それは「安全」に依存します。割り当て自体に関する情報がポインタと共に格納されるため、デアロケータが適切な場所に情報を返すことができるため、通常は機能します。この意味で、アロケーターが内部境界タグを使用している限り、「安全」です。(たくさんあります。)

ただし、他の回答で述べたように、voidポインタを削除してもデストラクタは呼び出されないため、問題になる可能性があります。その意味では「安全」ではありません。

自分がやっていることを、自分がやっていることをする正当な理由はありません。独自の割り当て解除関数を作成する場合は、関数テンプレートを使用して、正しいタイプの関数を生成できます。これを行う正当な理由は、プールアロケーターを生成することです。これは、特定のタイプに対して非常に効率的です。

他の回答で述べたように、これはC ++での未定義の動作です。一般に、トピック自体は複雑で意見の対立で満たされていますが、未定義の動作を回避することをお勧めします。


9
これはどのように受け入れられた答えですか?「無効なポインタを削除する」ことは意味がありません。安全性は重要なポイントです。
Kerrek SB 2013

6
「自分がやっていることを自分がやっていることをする正当な理由はない。」それはあなたの意見であり、事実ではありません。
rxantos 2015

8
@rxantos質問の作成者がやりたいことをすることがC ++で良い考えである反例を提供してください。
クリストファー

この答えは実際にはほとんど合理的だと思いますが、この質問に対するすべての答えは、これが未定義の動作であることを少なくとも言及する必要があると思います。
カイルストランド

@Christopherタイプ固有ではなく単純に機能する単一のガベージコレクタシステムを作成してみてください。sizeof(T*) == sizeof(U*)すべての人T,Uが、テンプレート化されていない、void *ベースのガベージコレクター実装を1つ持つことが可能であることを示唆しているという事実。しかし、その後、GCが実際にポインタを削除/解放する必要がある場合、まさにこの質問が発生します。これを機能させるには、ラムダ関数デストラクタラッパー(urgh)が必要か、または型と格納可能な型の間を行き来できる、ある種の動的な「データとしての型」が必要です。
BitTickler

147

voidポインタによる削除は、C ++標準では定義されていません。セクション5.3.5 / 3を参照してください。

最初の代替方法(オブジェクトの削除)では、オペランドの静的タイプが動的タイプと異なる場合、静的タイプはオペランドの動的タイプの基本クラスであり、静的タイプは仮想デストラクタを持つか、動作が未定義です。2番目の方法(配列の削除)では、削除するオブジェクトの動的タイプが静的タイプと異なる場合、動作は未定義です。

そしてその脚注:

これは、void型のオブジェクトがないため、void *型のポインターを使用してオブジェクトを削除できないことを意味します。


6
あなたは正しい見積もりに達しましたか?脚注はこのテキストを参照していると思います:「最初の代替(オブジェクトの削除)では、オペランドの静的タイプが動的タイプと異なる場合、静的タイプは、オペランドの動的タイプの基本クラスと静的タイプには仮想デストラクタがあるか、動作が未定義です。2番目の代替方法(配列の削除)では、削除するオブジェクトの動的タイプが静的タイプと異なる場合、動作は未定義です。:)
Johannes Schaub-litb

2
あなたは正しい-私は答えを更新しました。基本的なポイントを否定しているとは思いませんか?

いいえ、もちろん違います。それはまだそれがUBであると言います。さらに、void *の削除はUBであると規範的に規定されています:)
Johannes Schaub-litb 09年

voidポインターのポイントされたアドレスメモリNULLをアプリケーションメモリ管理に何らかの違いを持たせて埋めますか?
artu-hnrq

25

これは良い考えではなく、C ++で行うことでもありません。理由もなくタイプ情報を失っています。

デストラクタは、プリミティブ以外のタイプで呼び出すときに削除する配列内のオブジェクトでは呼び出されません。

代わりに、new / deleteをオーバーライドする必要があります。

void *を削除するとメモリが正しく解放される可能性がありますが、結果が定義されていないため、誤りです。

何らかの理由で不明な点があり、ポインタをvoid *に格納してから解放する必要がある場合は、mallocとfreeを使用する必要があります。


5
デストラクタが呼び出されないのは正しいですが、サイズが不明であることは間違っています。あなたが新しいから得たことを、ポインタを削除与える場合、それはない実際には完全に離れタイプから、事の大きさが削除されている知っています。それがどのように行われるかはC ++標準では指定されていませんが、「new」によって返されるポインターが指すデータの直前にサイズが格納される実装を見てきました。
KeyserSoze 2009年

C ++標準では未定義とされていますが、サイズに関する部分を削除しました。malloc / freeがvoid *ポインターでも機能することは知っています。
ブライアンR.ボンディ

標準の関連セクションへのWebリンクがあると思いませんか?私が調べたnew / deleteのいくつかの実装は、型の知識がなくても確実に正しく機能することは知っていますが、標準で指定されているものを見ていません。IIRC C ++は元々、配列を削除するときに配列要素数を必要としましたが、最新バージョンでは不要になりました。
KeyserSoze 2009年

@Neil Butterworthの回答をご覧ください。彼の答えは私の意見では受け入れられるべきものです。
ブライアンR.ボンディ

2
@keysersoze:一般的に私はあなたの発言に同意しません。いくつかの実装が割り当てられたメモリの前にサイズを保存したからといって、それが規則であるとは限りません。

13

voidポインタを削除すると、デストラクタが実際に指す値で呼び出されなくなるため、危険です。これにより、アプリケーションでメモリ/リソースリークが発生する可能性があります。


1
charにはコンストラクタ/デストラクタがありません。
rxantos 2015

9

その質問は意味がありません。あなたの混乱は、人々がよく使うずさんな言語が原因の一部かもしれませんdelete

動的に割り当てられdeleteオブジェクトを破棄するために使用します。そのためには、そのオブジェクトへポインターを使用して削除式を作成します。「ポインタを削除する」ことは決してありません。実際に行うことは、「アドレスで識別されるオブジェクトを削除する」ことです。

質問が意味をなさない理由がわかります:voidポインターは「オブジェクトのアドレス」ではありません。これは単なるアドレスであり、セマンティクスはありません。実際のオブジェクトのアドレスから取得された可能性がありますが、その情報は元のポインターのタイプでエンコードされているため、失われます。オブジェクトポインターを復元する唯一の方法は、voidポインターをオブジェクトポインターにキャストすることです(これには、作成者がポインターの意味を知っている必要があります)。voidそれ自体は不完全な型であり、したがってオブジェクトの型になることはありません。また、voidポインターを使用してオブジェクトを識別することもできません。(オブジェクトは、タイプとアドレスによって共同で識別されます。)


確かに、この質問は周囲の状況がないとあまり意味がありません。一部のC ++コンパイラーは、そのような無意味なコードを引き続き喜んでコンパイルします(役立つと感じた場合は、それに関する警告が表示されることがあります)。そのため、この不適切なアドバイスが含まれているレガシーコードを実行する既知のリスクを評価するために質問が行われました。文字配列メモリの一部またはすべてをリークしますか?プラットフォーム固有の他の何か?
An̲̳̳drew 2013年

丁寧な対応ありがとうございます。賛成!
An̲̳̳drew 2013年

1
@Andrew:標準はこれでかなり明確だと思います:「のオペランドの値は、deletenullポインター値、以前のnew-expressionによって作成された非配列オブジェクトへのポインター、またはそのようなオブジェクトの基本クラスを表すサブオブジェクト。そうでない場合、動作は未定義です。」したがって、コンパイラが診断なしでコードを受け入れる場合、それはコンパイラのバグにすぎません...
Kerrek SB

1
@KerrekSB- コンパイラのバグにすぎません -同意しません。標準では、動作は定義されていません。これは、コンパイラー/実装が何でも実行でき、標準に準拠していることを意味します。コンパイラーの応答がvoid *ポインターを削除できないと言う場合は、それで問題ありません。コンパイラの応答がハードドライブを消去することである場合、それも問題ありません。OTOH、コンパイラーの応答が診断を生成せず、代わりにそのポインターに関連付けられたメモリーを解放するコードを生成する場合、それも問題ありません。これは、この形式のUBを処理する簡単な方法です。
David Hammen 2014

追加するだけです。私はの使用を容認していませんdelete void_pointer。これは未定義の動作です。プログラマーが望んでいたことを応答が実行しているように見えても、プログラマーは未定義の動作を呼び出すべきではありません。
David Hammen 2014

6

本当にこれを行う必要がある場合は、仲介者(newおよびdelete演算子)を切り取り、グローバルoperator newoperator delete直接呼び出しませんか?(もちろん、newand delete演算子を計測しようとしている場合は、実際にoperator newand を再実装する必要がありoperator deleteます。)

void* my_alloc (size_t size)
{
   return ::operator new(size);
}

void my_free (void* ptr)
{
   ::operator delete(ptr);
}

とは異なりmalloc()、失敗時にoperator newスローstd::bad_allocします(または、new_handler登録されている場合はを呼び出します)。


charにはコンストラクタ/デストラクタがないため、これは正しいです。
rxantos 2015

5

charには特別なデストラクタロジックがないためです。これは機能しません。

class foo
{
   ~foo() { printf("huzza"); }
}

main()
{
   foo * myFoo = new foo();
   delete ((void*)foo);
}

d'ctorは呼び出されません。


5

void *を使用したい場合は、なぜmalloc / freeだけを使用しないのですか?新規/削除は単なるメモリ管理ではありません。基本的に、new / deleteはコンストラクタ/デストラクタを呼び出し、さらに多くの処理が行われます。組み込み型(char *など)のみを使用し、void *を介してそれらを削除する場合は機能しますが、お勧めできません。要点は、void *を使用する場合はmalloc / freeを使用することです。それ以外の場合は、便利なテンプレート関数を使用できます。

template<typename T>
T* my_alloc (size_t size)
{
   return new T [size];
}

template<typename T>
void my_free (T* ptr)
{
   delete [] ptr;
}

int main(void)
{
    char* pChar = my_alloc<char>(10);
    my_free(pChar);
}

例ではコードを記述しませんでした。このパターンがいくつかの場所で使用されていることに偶然出くわし、不思議なことにC / C ++のメモリ管理が混在していて、具体的な危険は何なのかと思っていました。
An̲̳̳drew 2009年

C / C ++の作成は失敗のレシピです。それを書いた人は、どちらか一方を書くべきでした。
David Thornley、

2
@DavidそれはC ++であり、C / C ++ではありません。Cにはテンプレートがなく、新規および削除を使用しません。
rxantos 2015

5

多くの人々はすでに、「いいえ、voidポインターを削除するのは安全ではない」とコメントしています。私もそれに同意しますが、連続した配列などを割り当てるためにvoidポインターnewを使用している場合は、これを使用してdelete安全に(with、ahem 、少し余分な作業)。これは、メモリ領域(「アリーナ」と呼ばれます)にvoidポインターを割り当て、アリーナへのポインターをnewに提供することで行われます。C ++ FAQのこのセクションを参照してください。これは、C ++でメモリプールを実装するための一般的なアプローチです。


0

これを行う理由はほとんどありません。

まず、データのタイプがわからずvoid*、それがであることがわかっている場合、実際には、そのデータをタイプなしのバイナリデータのブロブunsigned char*)として扱い、malloc/ freeを使用して処理する必要があります。 。これはvoid*、C apiへのポインタを渡す必要がある波形データなどの場合に必要になることがあります。それはいいです。

あなたがいる場合行うデータの種類を(すなわち、それはCTOR /デストラクタを持っている)知っているが、何らかの理由であなたが終わったvoid*(何らかの理由であなたが持っている)、ポインタ、あなたが本当に型に戻ってそれをキャストする必要があり、あなたがそれを知っていますあり、それを要求deleteしなさい。


0

私はフレームワークでvoid *(別名:不明な型)をコードリフレクションやその他のあいまいさの妙技で使用しましたが、これまでのところ、どのコンパイラからも問題(メモリリーク、アクセス違反など)は発生していません。非標準の操作による警告のみ。

不明(void *)を削除することは完全に理にかなっています。ポインタがこれらのガイドラインに従っていることを確認してください。そうしないと、意味がなくなる場合があります。

1)不明なポインターは、簡単なデコンストラクターを持つ型を指してはなりません。そのため、不明なポインターとしてキャストされた場合、決して削除してはなりません。不明なポインタは、元の型にキャストした後でのみ削除してください。

2)インスタンスはスタックバウンドまたはヒープバウンドメモリで不明なポインタとして参照されていますか?不明なポインタがスタック上のインスタンスを参照している場合は、削除しないでください。

3)不明なポインタが有効なメモリ領域であると100%肯定していますか?いいえ、それは決して削除されるべきではありません!

全体として、不明な(void *)ポインター型を使用して実行できる直接的な作業はほとんどありません。ただし、間接的に、void *は、C ++開発者がデータのあいまいさが必要な場合に信頼できる優れた資産です。


0

バッファだけが必要な場合は、malloc / freeを使用してください。new / deleteを使用する必要がある場合は、簡単なラッパークラスを検討してください。

template<int size_ > struct size_buffer { 
  char data_[ size_]; 
  operator void*() { return (void*)&data_; }
};

typedef sized_buffer<100> OpaqueBuffer; // logical description of your sized buffer

OpaqueBuffer* ptr = new OpaqueBuffer();

delete ptr;

0

charの特定の場合。

charは、特別なデストラクタを持たない組み込み型です。したがって、リークの引数は根拠のないものです。

sizeof(char)は通常1であるため、alignment引数もありません。sizeof(char)が1でないまれなプラットフォームの場合、charに対して十分に整列されたメモリを割り当てます。したがって、整列の議論も根拠のないものです。

この場合、malloc / freeの方が高速です。しかし、あなたはstd :: bad_allocを放棄し、mallocの結果をチェックする必要があります。グローバルなnewおよびdeleteオペレーターを呼び出すと、仲介者を回避できるため、より適切な場合があります。


1
" sizeof(char)は通常1つです " sizeof(char)は
常に

最近(2019年)までは、new実際にはスローするように定義されていると人々は考えていません。本当じゃない。コンパイラとコンパイラスイッチに依存します。たとえば、MSVC2019 /GX[-] enable C++ EH (same as /EHsc)スイッチを参照してください。また、組み込みシステムでは、多くの人がC ++例外のパフォーマンス税を支払わないことを選択しています。したがって、「しかし、あなたはstd :: bad_alloc ...を失う」という文は疑わしいものです。
BitTickler
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.