web-dev-qa-db-ja.com

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

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

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

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

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

87
An̲̳̳drew

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

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

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

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

22
Christopher

Voidポインターを使用した削除は、C++標準では未定義です-セクション5.3.5/3を参照:

最初の選択肢(オブジェクトの削除)では、オペランドの静的な型がその動的な型と異なる場合、静的な型はオペランドの動的な型の基本クラスであり、静的な型には仮想デストラクタがあるか、動作が未定義です。 2番目の選択肢(配列の削除)では、削除するオブジェクトの動的な型が静的な型と異なる場合、動作は未定義です。

そしてその脚注:

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

137
anon

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

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

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

Void *を削除すると、メモリが偶然正しく解放される可能性がありますが、結果は未定義であるため間違っています。

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

24
Brian R. Bondy

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

13
JaredPar

質問は意味がありません。あなたの混乱は、人々がdeleteでよく使うだらしのない言語のせいかもしれません:

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

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

7
Kerrek SB

本当にこれをしなければならないのであれば、中間者(newおよびdelete演算子)を切り取り、グローバル_operator new_および_operator delete_を直接呼び出してみませんか? (もちろん、newおよびdelete演算子をインストルメントしようとする場合、実際には_operator new_および_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_を呼び出します)。

5
bk1e

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

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

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

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

5
radu

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

5
Paul Morie

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);
}
5
young

バッファだけが必要な場合は、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
Sanjaya R

Charの特定の場合。

charは、特別なデストラクタを持たない組み込み型です。したがって、leaks引数は重要ではありません。

sizeof(char)は通常1なので、アライメント引数もありません。 sizeof(char)が1ではないまれなプラットフォームの場合、charに対して十分にアライメントされたメモリを割り当てます。したがって、配置の引数も重要ではありません。

この場合、malloc/freeは高速になります。しかし、std :: bad_allocを失い、mallocの結果を確認する必要があります。グローバルのnew演算子とdelete演算子を呼び出すと、仲介者がバイパスされるため、より適切な場合があります。

0
rxantos

私はフレームワークでvoid *(別名未知の型)をコードのリフレクションやその他のあいまいさの偉業で使用しましたが、これまでのところ、コンパイラーからのトラブル(メモリリーク、アクセス違反など)はありませんでした。操作が非標準であることによる警告のみ。

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

1)不明なポインターは、些細な解体子を持つ型を指してはならないので、不明なポインターとしてキャストされた場合、決して削除されるべきではありません。未知のポインターを削除するのは、元の型にキャストしてからにしてください。

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

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

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

0
zackery.fix

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

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

doデータのタイプを知っている(つまり、ctor/dtorを持っている)が、何らかの理由でvoid*ポインター(何らかの理由で)になった場合- それから、あなたがそれを知っているタイプに本当に戻すべきです、そしてそれに対してdeleteを呼び出します。

0
bobobobo