Array placement-new requires unspecified overhead in the buffer?

前端 未结 6 1186
甜味超标
甜味超标 2020-11-22 15:09

5.3.4 [expr.new] of the C++11 Feb draft gives the example:

new(2,f) T[5] results in a call of operator new[](sizeof(T)*5+

相关标签:
6条回答
  • 2020-11-22 15:48

    This overhead may be applied in all array new-expressions, including those referencing the library function operator new[](std::size_t, void*) and other placement allocation functions.

    This is a defect in the standard. Rumor has it they couldn't find a volunteer to write an exception to it (Message #1173).

    The non-replaceable array placement-new cannot be used with delete[] expressions, so you need to loop through the array and call each destructor.

    The overhead is targetted at the user-defined array placement-new functions, which allocate memory just like the regular T* tp = new T[length]. Those are compatible with delete[], hence the overhead that carries the array length.

    0 讨论(0)
  • 2020-11-22 15:53

    As mentioned by Kerrek SB in comments, this defect was first reported in 2004, and it was resolved in 2012 as:

    The CWG agreed that EWG is the appropriate venue for dealing with this issue.

    Then the defect was reported to EWG in 2013, but closed as NAD (presumably means "Not A Defect") with the comment:

    The problem is in trying to use array new to put an array into pre-existing storage. We don't need to use array new for that; just construct them.

    which presumably means that the suggested workaround is to use a loop with a call to non-array placement new once for each object being constructed.


    A corollary not mentioned elsewhere on the thread is that this code causes undefined behaviour for all T:

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

    Even if we comply with the lifetime rules (i.e. T either has trivial destruction, or the program does not depend on the destructor's side-effects), the problem is that ptr has been adjusted for this unspecified cookie, so it is the wrong value to pass to operator delete[].

    0 讨论(0)
  • 2020-11-22 15:55

    After reading corresponding standard sections I am satarting to think that placement new for array types is simply useless idea, and the only reason for it being allowed by standard is generic way in which new-operator is described:

    The new expression attempts to create an object of the typeid (8.1) or newtypeid to which it is applied. The type of that object is the allocated type. This type shall be a complete object type, but not an abstract class type or array thereof (1.8, 3.9, 10.4). [Note: because references are not objects, references cannot be created by newexpressions. ] [Note: the typeid may be a cvqualified type, in which case the object created by the newexpression has a cvqualified type. ]

    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) )
    

    To me it seems that array placement new simply stems from compactness of the definition (all possible uses as one scheme), and it seems there is no good reason for it to be forbidden.

    This leaves us in a situation where we have useless operator, which needs memory allocated before it is known how much of it will be needed. The only solutions I see would be to either overallocate memory and hope that compiler will not want more than supplied, or re-allocate memory in overriden array placement new function/method (which rather defeats the purpose of using array placement new in the first place).


    To answer question pointed out by Kerrek SB: Your example:

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

    is not always correct. In most implementations arr!=addr (and there are good reasons for it) so your code is not valid, and your buffer will be overrun.

    About those "good reasons" - note that you are released by standard creators from some house-keeping when using array new operator, and array placement new is no different in this respect. Note that you do not need to inform delete[] about length of array, so this information must be kept in the array itself. Where? Exactly in this extra memory. Without it delete[]'ing would require keeping array length separate (as stl does using loops and non-placement new)

    0 讨论(0)
  • 2020-11-22 16:01

    Don't use operator new[](std::size_t, void* p) unless you know a-priori the answer to this question. The answer is an implementation detail and can change with compiler/platform. Though it is typically stable for any given platform. E.g. this is something specified by the Itanium ABI.

    If you don't know the answer to this question, write your own placement array new that can check this at run time:

    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];
    }
    

    By varying the array size and inspecting n in the example above, you can infer y for your platform. For my platform y is 1 word. The sizeof(word) varies depending on whether I'm compiling for a 32 bit or 64 bit architecture.

    0 讨论(0)
  • 2020-11-22 16:03

    Update: After some discussion, I understand that my answer no longer applies to the question. I'll leave it here, but a real answer is definitely still called for.

    I'll be happy to support this question with some bounty if a good answer isn't found soon.

    I'll restate the question here as far as I understand it, hoping that a shorter version might help others understand what's being asked. The question is:

    Is the following construction always correct? Is arr == addr at the end?

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

    We know from the standard that #1 causes the call ::operator new[](???, addr), where ??? is an unspecified number no smaller than N * sizeof(T), and we also know that that call only returns addr and has no other effects. We also know that arr is offset from addr correspondingly. What we do not know is whether the memory pointed to by addr is sufficiently large, or how we would know how much memory to allocate.


    You seem to confuse a few things:

    1. Your example calls operator new[](), not operator new().

    2. The allocation functions do not construct anything. They allocate.

    What happens is that the expression T * p = new T[10]; causes:

    1. a call to operator new[]() with size argument 10 * sizeof(T) + x,

    2. ten calls to the default constructor of T, effectively ::new (p + i) T().

    The only peculiarity is that the array-new expression asks for more memory than what is used by the array data itself. You don't see any of this and cannot make use of this information in any way other than by silent acceptance.


    If you are curious how much memory was actually allocated, you can simply replace the array allocation functions operator new[] and operator delete[] and make it print out the actual size.


    Update: As a random piece of information, you should note that the global placement-new functions are required to be no-ops. That is, when you construct an object or array in-place like so:

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

    Then the corresponding calls to ::operator new(std::size_t, void*) and ::operator new[](std::size_t, void*) do nothing but return their second argument. However, you do not know what buf10 is supposed to point to: It needs to point to 10 * sizeof(T) + y bytes of memory, but you cannot know y.

    0 讨论(0)
  • 2020-11-22 16:10

    Calling any version of operator new[] () won't work too well with a fixed size memory area. Essentially, it is assumed that it delegates to some real memory allocation function rather than just returning a pointer to the allocated memory. If you already have a memory arena where you want to construct an array of objects, you want to use std::uninitialized_fill() or std::uninitialized_copy() to construct the objects (or some other form of individually constructing the objects).

    You might argue that this means that you have to destroy the objects in your memory arena manually as well. However, calling delete[] array on the pointer returned from the placement new won't work: it would use the non-placement version of operator delete[] ()! That is, when using placement new you need to manually destroy the object(s) and release the memory.

    0 讨论(0)
提交回复
热议问题