66

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

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の呼び出しごとに異なる場合があります。—例を終了]

次に、次のサンプルコードを使用します。

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::stringy > 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*)

4

7 に答える 7

48

アップデート

Nicol Bolasは、以下のコメントで、オーバーヘッドが常にゼロになるoperator new[](std::size_t, void* p)ように修正されていることを正しく指摘しています。

この修正は2019年11月に欠陥レポートとして行われ、C++のすべてのバージョンに遡及します。

元の回答

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

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

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ビットアーキテクチャのどちらでコンパイルするかによって異なります。

于 2012-01-04T04:13:38.313 に答える
9

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

すぐに良い答えが見つからない場合は、この質問を喜んでサポートします。

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

次の構造は常に正しいですか?arr == addr終わりですか?

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

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


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

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

  2. 割り当て関数は何も構築しません。彼らは割り当てます。

何が起こるかというと、その T * p = new T[10];は次のことを引き起こします。

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

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

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


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


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

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

于 2012-01-04T00:05:11.347 に答える
7

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

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

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

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

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


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

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

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

于 2016-03-10T20:42:35.983 に答える
6

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

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

于 2012-01-04T05:58:38.167 に答える
3

C++20はこの答えを変えることに注意してください。

C ++ 17(およびそれ以前)の[expr.new] / 11は、この関数そのサイズに対して実装定義のオフセットを取得する可能性があることを明確に示しています。

new-expressionが割り当て関数を呼び出し、その割り当てが拡張されていない場合、new-expressionは、要求されたスペースの量をstd::size_t型の最初の引数として割り当て関数に渡します。その引数は、作成されるオブジェクトのサイズ以上でなければなりません。オブジェクトが配列である場合にのみ、作成されるオブジェクトのサイズよりも大きくなる可能性があります。

これにより、配列割り当て関数に指定されたサイズをから増やすことができますが、必須ではありませんsizeof(T) * size

C++20はこれを明示的に禁止しています。[expr.new] / 15から:

new-expressionが割り当て関数を呼び出し、その割り当てが拡張されていない場合、new-expressionは、要求されたスペースの量をstd::size_t型の最初の引数として割り当て関数に渡します。その引数は、作成されるオブジェクトのサイズ以上でなければなりません。オブジェクトが配列であり、割り当て関数が非割り当て形式([new.delete.placement] )でない場合にのみ、作成されるオブジェクトのサイズよりも大きくなる可能性があります。

強調が追加されました。あなたが引用した非規範的なメモでさえ変更されました:

このオーバーヘッドは、ライブラリ関数演算子new [](std :: size_t、void *)を参照する場合を除いて、配置割り当て関数を参照するものを含むすべての配列new-expressionに適用できます。

于 2021-03-21T19:21:35.867 に答える
1

対応する標準セクションを読んだ後、配列型の新しい配置は単に役に立たない考えであり、標準で許可される唯一の理由は、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[]、配列の長さを分離しておく必要があります(stlがループと非配置を使用する場合のようにnew

于 2012-01-04T22:54:54.663 に答える
1

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

これは規格の欠陥です。噂によると、例外を書くボランティアを見つけることができなかったということです(メッセージ#1173)。

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

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

于 2016-04-26T03:51:51.500 に答える