配列の新しい配置を移植可能な方法で使用できますか?
-
08-06-2019 - |
質問
実際に配列に使用する場合、ポータブル コードで配置を利用することは可能ですか?
new[] から返されるポインタは、渡したアドレスと必ずしも同じではないようです (標準の 5.3.4、注 12 でこれが正しいことが確認されているようです)。この場合、配列に入力するバッファを割り当てることができます。
次の例は問題を示しています。この例を Visual Studio でコンパイルすると、メモリ破損が発生します。
#include <new>
#include <stdio.h>
class A
{
public:
A() : data(0) {}
virtual ~A() {}
int data;
};
int main()
{
const int NUMELEMENTS=20;
char *pBuffer = new char[NUMELEMENTS*sizeof(A)];
A *pA = new(pBuffer) A[NUMELEMENTS];
// With VC++, pA will be four bytes higher than pBuffer
printf("Buffer address: %x, Array address: %x\n", pBuffer, pA);
// Debug runtime will assert here due to heap corruption
delete[] pBuffer;
return 0;
}
メモリを見ると、コンパイラはバッファ内の項目数のカウントを保存するためにバッファの最初の 4 バイトを使用しているようです。これは、バッファーが sizeof(A)*NUMELEMENTS
大きい場合、配列の最後の要素が未割り当てのヒープに書き込まれます。
そこで質問は、配置 new[] を安全に使用するために、実装でどのくらいの追加オーバーヘッドが必要かを知ることができるかということです。理想的には、異なるコンパイラ間で移植可能な技術が必要です。少なくとも VC の場合、オーバーヘッドはクラスごとに異なるようであることに注意してください。たとえば、例の仮想デストラクターを削除した場合、new[] から返されるアドレスは、渡したアドレスと同じになります。
解決
個人的には、配列に placement new を使用せず、代わりに配列内の各項目に個別に placement new を使用するというオプションを選択します。例えば:
int main(int argc, char* argv[])
{
const int NUMELEMENTS=20;
char *pBuffer = new char[NUMELEMENTS*sizeof(A)];
A *pA = (A*)pBuffer;
for(int i = 0; i < NUMELEMENTS; ++i)
{
pA[i] = new (pA + i) A();
}
printf("Buffer address: %x, Array address: %x\n", pBuffer, pA);
// dont forget to destroy!
for(int i = 0; i < NUMELEMENTS; ++i)
{
pA[i].~A();
}
delete[] pBuffer;
return 0;
}
使用する方法に関係なく、リークが発生する可能性があるため、pBuffer を削除する前に必ず配列内の各項目を手動で破棄してください ;)
注記:これはコンパイルしていませんが、動作するはずです (C++ コンパイラがインストールされていないマシンを使用しています)。それはまだ要点を示しています:) 何らかの形で役立つことを願っています!
編集:
要素の数を追跡する必要がある理由は、配列に対して delete を呼び出すときに要素を反復処理し、各オブジェクトに対してデストラクターが呼び出されることを確認するためです。どれだけあるのか分からない場合は、これを行うことはできません。
他のヒント
@デレク
5.3.4 のセクション 12 では、配列割り当てのオーバーヘッドについて説明しており、私の読み違いでない限り、コンパイラが new 配置時に追加することも有効であることを示唆しているようです。
このオーバーヘッドは、ライブラリ関数演算子 new[](std::size_t, void*) やその他の配置割り当て関数を参照するものを含む、すべての配列 new-expressions に適用される可能性があります。オーバーヘッドの量は、new の呼び出しごとに異なる場合があります。
とはいえ、GCC、Codewarrior、ProDG のうち、この問題で問題が発生したコンパイラーは VC だけだったと思います。念のためもう一度確認する必要がありますが。
ご返信ありがとうございます。配列内の各項目に配置 new を使用することが、これに遭遇したときに最終的に使用した解決策でした(申し訳ありませんが、質問で言及する必要がありました)。配置 new[] を使用してそれを行うには、何かが欠けていたに違いないと感じました。現状では、コンパイラーが配列に追加の不特定のオーバーヘッドを追加できるようにする標準のおかげで、配置 new[] は本質的に使用できないようです。どうすれば安全かつ持ち運びに便利に使用できるのかわかりません。
いずれにせよ、配列に対して delete[] を呼び出すことはないので、追加のデータが必要な理由さえよくわかりません。そのため、配列内の項目の数を知る必要がある理由が完全にはわかりません。
@ジェームズ
いずれにせよ、配列に対して delete[] を呼び出すことはないので、追加のデータが必要な理由さえよくわかりません。そのため、配列内の項目の数を知る必要がある理由が完全にはわかりません。
少し考えた結果、私はあなたに同意します。配置の削除がないため、配置の新規で要素の数を保存する必要がある理由はありません。配置の削除がないため、配置の新規に要素の数を保存する理由はありません。
また、デストラクターを持つクラスを使用して、Mac 上の gcc でこれをテストしました。私のシステムでは、新しい配置は ない ポインタを変更します。これは、これが VC++ の問題なのか、また標準に違反しているのではないかと疑問に思います (私が見つけた限り、標準はこれに具体的に対処していません)。
新しい配置自体は移植可能ですが、メモリの指定されたブロックでの動作についての仮定は移植可能ではありません。前に述べたように、あなたがコンパイラで、メモリのチャンクを与えられた場合、ポインタしかない場合、配列を割り当て、各要素を適切に破棄する方法をどうやって知ることができるでしょうか?(オペレーター delete[] のインターフェースを参照してください。)
編集:
そして、実際には配置削除がありますが、配置 new[] で配列を割り当てるときにコンストラクターが例外をスローした場合にのみ呼び出されます。
new[] が実際に何らかの方法で要素の数を追跡する必要があるかどうかは標準に委ねられており、標準はコンパイラに任せています。残念ながら、この場合は。
gcc は MSVC と同じことを行うと思いますが、もちろんこれで「ポータブル」になるわけではありません。
次のように、NUMELEMENTS が実際にコンパイル時定数である場合、問題を回避できると思います。
typedef A Arr[NUMELEMENTS];
A* p = new (buffer) Arr;
これには、新しいスカラー配置を使用する必要があります。
単一の要素を使用して 1 つの配置 (新規) のサイズを計算する方法と同様に、それらの要素の配列を使用して配列に必要なサイズを計算します。
要素数が不明な他の計算でサイズが必要な場合は、sizeof(A[1]) を使用して、必要な要素数を掛けることができます。
例えば
char *pBuffer = new char[ sizeof(A[NUMELEMENTS]) ];
A *pA = (A*)pBuffer;
for(int i = 0; i < NUMELEMENTS; ++i)
{
pA[i] = new (pA + i) A();
}