web-dev-qa-db-ja.com

アレイの配置-新規では、バッファに不特定のオーバーヘッドが必要ですか?

5.3.4 C++ 11 Febドラフトの_[expr.new]_に例を示します。

new(2,f) T[5]は、operator new[](sizeof(T)*5+y,2,f)の呼び出しになります。

ここで、xとyは、配列割り当てのオーバーヘッドを表す非負の不特定の値です。 new-expressionの結果は、_operator new[]_によって返される値からこの量だけオフセットされます。このオーバーヘッドは、ライブラリ関数operator new[](std::size_t, void*)およびその他の配置割り当て関数を参照するものを含むすべての配列new-expressionsに適用される可能性があります。オーバーヘッドの量は、newの呼び出しごとに異なる場合があります。 —例を終了]

次のサンプルコードを見てください。

_void* buffer = malloc(sizeof(std::string) * 10);
std::string* p = ::new (buffer) std::string[10];
_

上記の引用によると、2行目のnew (buffer) std::string[10]は内部的にoperator new[](sizeof(std::string) * 10 + y, buffer)を呼び出します(個々の_std::string_オブジェクトを作成する前に)。問題は、_y > 0_の場合、事前に割り当てられたバッファが小さすぎることです。

では、配列配置を使用するときに事前に割り当てるメモリの量をどのように知ることができますか?

_void* buffer = malloc(sizeof(std::string) * 10 + how_much_additional_space);
std::string* p = ::new (buffer) std::string[10];
_

または、この場合、標準はどこかで_y == 0_を保証しますか?繰り返しますが、引用は次のように述べています。

このオーバーヘッドは、ライブラリ関数operator new[](std::size_t, void*)およびその他の配置割り当て関数を参照するものを含むすべての配列new-expressionsに適用される可能性があります。

62
Mooing Duck

この質問に対する答えを事前に知っていない限り、operator new[](std::size_t, void* p)を使用しないでください。答えは実装の詳細であり、コンパイラ/プラットフォームによって変わる可能性があります。通常、特定のプラットフォームで安定していますが。例えば。これは Itanium ABI で指定されたものです。

この質問に対する答えがわからない場合は、実行時にこれを確認できる独自の配置配列を新しく作成してください。

inline
void*
operator new[](std::size_t n, void* p, std::size_t limit)
{
    if (n <= limit)
        std::cout << "life is good\n";
    else
        throw std::bad_alloc();
    return p;
}

int main()
{
    alignas(std::string) char buffer[100];
    std::string* p = new(buffer, sizeof(buffer)) std::string[3];
}

上記の例で配列サイズを変更してnを調べることにより、プラットフォームのyを推測できます。 私のプラットフォームyは1ワードです。 sizeof(Word)は、32ビットアーキテクチャと64ビットアーキテクチャのどちらでコンパイルするかによって異なります。

42
Howard Hinnant

更新:いくつかの議論の後、私の答えはもはや質問に当てはまらないことを理解しています。ここに残しておきますが、本当の答えはまだまだ求められています。

すぐに良い答えが見つからない場合は、この質問を賞金でサポートさせていただきます。

私が理解している限り、ここで質問を言い直します。短いバージョンが、他の人が質問されていることを理解するのに役立つことを願っています。質問は:

次の構造は常に正しいですか?最後に_arr == addr_はありますか?

_void * addr = std::malloc(N * sizeof(T));
T * arr = ::new (addr) T[N];                // #1
_

標準から、#1が::operator new[](???, addr)の呼び出しを引き起こすことがわかっています。ここで、_???_はN * sizeof(T)以上の不特定の番号であり、その呼び出しはaddrのみを返すこともわかっています。他の効果はありません。また、それに応じてarraddrからオフセットされていることもわかっています。私たちがしていることnotは、addrが指すメモリが十分に大きいかどうか、または割り当てるメモリの量をどのように知るかです。


あなたはいくつかのことを混乱させているようです:

  1. あなたの例はoperator new[]()を呼び出しますが、 operator new()

  2. 割り当て関数は何も構築しません。それらallocate

_T * p = new T[10];_の原因は次のとおりです。

  1. サイズ引数operator new[]()を使用した10 * sizeof(T) + xの呼び出し。

  2. Tのデフォルトコンストラクターへの10回の呼び出し、事実上::new (p + i) T()

唯一の特徴は、array-newexpressionが、配列データ自体で使用されるよりも多くのメモリを要求することです。あなたはこれのどれも見ず、黙って受け入れる以外の方法でこの情報を利用することはできません。


実際に割り当てられたメモリの量が知りたい場合は、配列割り当て関数_operator new[]_と_operator delete[]_を置き換えるだけで、実際のサイズを出力できます。


更新:ランダムな情報として、グローバル配置-new functionsはno-である必要があることに注意してください。 ops。つまり、次のようにオブジェクトまたは配列をインプレースで構築する場合:

_T * p = ::new (buf1) T;
T * arr = ::new (buf10) T[10];
_

次に、対応する::operator new(std::size_t, void*)および::operator new[](std::size_t, void*)の呼び出しは、2番目の引数を返すだけです。ただし、_buf10_が何を指しているのかわかりません。10 * sizeof(T) + yバイトのメモリを指している必要がありますが、yを知ることはできません。

8
Kerrek SB

operator new[] ()の任意のバージョンを呼び出すと、固定サイズのメモリ領域ではうまく機能しません。基本的に、割り当てられたメモリへのポインタを返すだけでなく、実際のメモリ割り当て関数に委任することを前提としています。オブジェクトの配列を作成するメモリ領域がすでにある場合は、std::uninitialized_fill()またはstd::uninitialized_copy()を使用してオブジェクト(またはオブジェクトを個別に作成する他の形式)を作成します。 )。

これは、メモリアリーナ内のオブジェクトも手動で破棄する必要があることを意味すると主張するかもしれません。ただし、プレースメントnewから返されたポインタで_delete[] array_を呼び出すことはできません。つまり、プレースメント以外のバージョンのoperator delete[] ()を使用します。つまり、プレースメントnewを使用する場合は、オブジェクトを手動で破棄してメモリを解放する必要があります。

6
Dietmar Kühl

Kerrek SBがコメントで述べたように、この欠陥は最初に報告されました 2004年 、そして2012年に次のように解決されました:

CWGは、EWGがこの問題に対処するための適切な場所であることに同意しました。

その後、欠陥は 2013年にEWGに報告 でしたが、コメント付きでNAD(おそらく「欠陥ではない」を意味します)としてクローズされました。

問題は、配列newを使用して、既存のストレージに配列を配置しようとすることです。そのために新しい配列を使用する必要はありません。それらを構築するだけです。

これはおそらく、推奨される回避策は、構築されているオブジェクトごとに1回、新しい非配列配置の呼び出しでループを使用することであることを意味します。


スレッドの他の場所で言及されていない当然の結果として、このコードはすべてのTに対して未定義の動作を引き起こします。

T *ptr = new T[N];
::operator delete[](ptr);

ライフタイムルールに準拠している場合でも(つまり、Tが些細な破壊であるか、プログラムがデストラクタの副作用に依存していない場合)、問題はptrがこれに合わせて調整されていることです。クッキーが指定されていないため、operator delete[]に渡すのは間違った値です。

5
M.M

対応する標準セクションを読んだ後、配列型の新しい配置は単に役に立たない考えであり、標準で許可される唯一の理由は、new-operatorを説明する一般的な方法であると考えています。

新しい式は、それが適用されるtypeid(8.1)またはnewtypeidのオブジェクトを作成しようとします。そのオブジェクトのタイプは、割り当てられたタイプです。この型は完全なオブジェクト型でなければなりませんが、抽象クラス型またはその配列(1.8、3.9、10.4)であってはなりません。 [注:参照はオブジェクトではないため、newexpressionsで参照を作成することはできません。 ] [注:typeidはcvqualifiedタイプである可能性があります。その場合、newexpressionによって作成されたオブジェクトはcvqualifiedタイプになります。 ]

new-expression: 
    ::(opt) new new-placement(opt) new-type-id new-initializer(opt)
    ::(opt) new new-placement(opt) ( type-id ) new-initializer(opt)

new-placement: ( expression-list )

newtypeid:
    type-specifier-seq new-declarator(opt)

new-declarator:
    ptr-operator new-declarator(opt)
    direct-new-declarator

direct-new-declarator:
    [ expression ]
    direct-new-declarator [ constant-expression ]

new-initializer: ( expression-list(opt) )

私には、array placement newは単に定義のコンパクトさ(1つのスキームとしてのすべての可能な使用法)に由来しているように思われ、それが禁止される正当な理由はないようです。

これにより、必要なメモリの量がわかる前にメモリを割り当てる必要がある、役に立たない演算子がある状況になります。私が見る唯一の解決策は、メモリを過剰に割り当ててコンパイラが提供された以上のものを望まないことを期待するか、オーバーライドされたarray placement new関数/メソッドでメモリを再割り当てすることです(これはarray placement newを使用する目的をむしろ無効にしますそもそも)。


Kerrek SBが指摘した質問に答えるには:あなたの例:

void * addr = std::malloc(N * sizeof(T));
T * arr = ::new (addr) T[N];                // #1

常に正しいとは限りません。ほとんどの実装ではarr!=addr(そしてそれには正当な理由があります)なので、コードは無効であり、バッファーがオーバーランします。

これらの「正当な理由」について-array new演算子を使用すると、標準の作成者によってハウスキーピングから解放されることに注意してください。この点でarray placement newも同じです。配列の長さについてdelete[]に通知する必要はないため、この情報は配列自体に保持する必要があることに注意してください。どこ?まさにこの余分なメモリに。それがないと、delete[]'ingは配列の長さを分離しておく必要があります(stlがループと非配置newを使用するように)

1
j_kubik

このオーバーヘッドは、ライブラリ関数operator new[](std::size_t, void*)およびその他の配置割り当て関数を参照するものを含め、すべての配列new-expressionsに適用される可能性があります。

これは規格の欠陥です。噂にはそれがあります 彼らはそれに例外を書くボランティアを見つけることができませんでした (メッセージ#1165)。

置き換え不可能な配列の配置-newdelete[]式では使用できないため、する必要があります配列をループして、各デストラクタを呼び出します

オーバーヘッドは、通常のT* tp = new T[length]と同じようにメモリを割り当てるユーザー定義の配列配置-new関数を対象としています。これらはdelete[]と互換性があるため、配列の長さを運ぶオーバーヘッドが発生します。

0
bit2shift