45

配列に使用する場合、移植可能なコードで新しい配置を実際に利用することは可能ですか?

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大きいだけであるため、配列の最後の要素が未割り当てヒープに書き込まれることを意味します。

質問は、placement new[] を安全に使用するために、実装が必要とする追加のオーバーヘッドをどれだけ見つけられるかということです。理想的には、異なるコンパイラ間で移植可能な手法が必要です。少なくとも VC の場合、オーバーヘッドはクラスによって異なるように見えることに注意してください。たとえば、例で仮想デストラクタを削除すると、new[] から返されるアドレスは、渡したアドレスと同じになります。

4

7 に答える 7

33

個人的には、配列で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 を呼び出すときにそれらを反復処理し、各オブジェクトでデストラクタが呼び出されるようにするためです。いくつあるかわからない場合、これを行うことはできません。

于 2008-08-18T22:53:05.003 に答える
5

@デレク

5.3.4、セクション12では、配列割り当てのオーバーヘッドについて説明しています。誤解しない限り、コンパイラが新しい配置に追加することも有効であると思われます。

このオーバーヘッドは、ライブラリ関数演算子new [](std :: size_t、void *)およびその他の配置割り当て関数を参照するものを含むすべての配列new-expressionに適用される可能性があります。オーバーヘッドの量は、newの呼び出しごとに異なる場合があります。

とは言うものの、VCは、GCC、Codewarrior、ProDGの中で、これに問題を引き起こした唯一のコンパイラだと思います。ただし、確認するためにもう一度確認する必要があります。

于 2008-08-19T10:16:44.577 に答える
4

@ジェームズ

とにかく配列でdelete[]を呼び出さないので、なぜ追加データが必要なのかはよくわかりません。そのため、配列に含まれるアイテムの数を知る必要がある理由が完全にはわかりません。

これを考えた後、私はあなたに同意します。配置の削除がないため、新しい配置で要素の数を格納する必要がある理由はありません。配置の削除がないため、要素の数を格納するために新しい配置を行う理由はありません。

また、デストラクタを備えたクラスを使用して、Macでgccを使用してこれをテストしました。私のシステムでは、新しい配置はポインターを変更していませんでした。これは、これがVC ++の問題であるかどうか、およびこれが標準に違反している可能性があるかどうかを疑問に思います(私が知る限り、標準はこれに特に対処していません)。

于 2008-08-19T02:14:14.067 に答える
3

Placement new 自体は移植可能ですが、指定されたメモリ ブロックで何を行うかについての仮定は移植可能ではありません。前に述べたように、あなたがコンパイラであり、メモリのチャンクが与えられた場合、配列を割り当てて各要素を適切に破棄する方法をどのように知ることができますか? (演算子 delete[] のインターフェイスを参照してください。)

編集:

そして、実際には配置の削除がありますが、配置 new[] を使用して配列を割り当てているときにコンストラクターが例外をスローした場合にのみ呼び出されます。

new[] が実際に何らかの形で要素の数を追跡する必要があるかどうかは、標準に任されているものであり、コンパイラに任されています。残念ながら、この場合。

于 2008-08-19T21:36:16.077 に答える
3

返信ありがとうございます。配列内の各アイテムに新しい配置を使用することは、これに遭遇したときに最終的に使用したソリューションでした(申し訳ありませんが、質問で言及する必要がありました)。配置 new[] でそれを行うには、何かが欠けていたに違いないと感じました。そのままでは、コンパイラが追加の未指定のオーバーヘッドを配列に追加できるようにする標準のおかげで、配置 new[] は本質的に使用できないようです。安全かつポータブルに使用する方法がわかりません。

とにかく配列でdelete []を呼び出さないので、追加のデータが必要な理由もよくわかりません。そのため、その中にいくつのアイテムがあるかを知る必要がある理由が完全にはわかりません。

于 2008-08-19T00:03:57.933 に答える
2

1 つの要素を使用して 1 つのplacement-new のサイズを計算する方法と同様に、これらの要素の配列を使用して、配列に必要なサイズを計算します。

要素の数がわからない可能性がある他の計算でサイズが必要な場合は、 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();
}
于 2008-08-18T23:26:37.463 に答える
1

gcc は MSVC と同じことをすると思いますが、もちろんこれは「移植可能」にはなりません。

NUMELEMENTS が実際にコンパイル時の定数である場合、次のように問題を回避できると思います。

typedef A Arr[NUMELEMENTS];

A* p = new (buffer) Arr;

これは、スカラー配置 new を使用する必要があります。

于 2008-08-18T21:45:14.640 に答える