3

In the following program I expected 10 bytes to be overwritten with placement new and then for the destructor to be called for each byte:

#include <memory>

struct MyChar {
    MyChar(char c = 'n') :c{c}{}
    ~MyChar(){ c = 'd'; }
    char c;
};

int main()
{
    {
        MyChar first[10]{0,1,2,3,4,5,6,7,8,9};
        new (first)MyChar[10]{10,11,12,13,14,15,16,17,18,19};
    }
    return 0;
}

But the compiler (*) warns that 18 will be written:

warning C6386: Buffer overrun while writing to 'first': the writable size is '10' bytes, but '18' bytes might be written.

At first the bytes are written as expected: enter image description here

But the compiler wasn't bluffing. At the placement new statement it does write 18 bytes: enter image description here

Which results in an error:

Run-Time Check Failure #2 - Stack around the variable 'first' was corrupted.

Why doesn't it stick to 10 bytes? sizeof(MyChar)==1 and alignof(MyChar)==1.


(*) Microsoft Visual Studio Community 2017 Preview (2). Also (but without warning during compilation) I get the same memory overwritten and runtime error on Microsoft Visual Studio Community 2017 (Version 15.2 (26430.15) Release).

wally
  • 10,717
  • 5
  • 39
  • 72
  • 6
    You can't placement new over an existing object without first calling it's destructor. You will want to read on [std::aligned_storage](http://en.cppreference.com/w/cpp/types/aligned_storage). – François Andrieux Oct 11 '17 at 20:09
  • which compiler? I cannot reproduce the warning on gcc, clang and MSVC. – bolov Oct 11 '17 at 20:26
  • @bolov Using: Microsoft Visual Studio Community 2017 Preview (2) – wally Oct 11 '17 at 20:28
  • 1
    One possible observed result of UB (see Francois on dtor). @FrançoisAndrieux: make that an answer. – lorro Oct 11 '17 at 20:30
  • @rex remove the ctor and dtor from the struct. That will make the struct a POD and remove the UB. See if the warning and behaviors is still the same. – bolov Oct 11 '17 at 20:34
  • @bolov Removing the constructor and destructor does remove the warning. But now it doesn't write the new data to memory. Strange. I'm testing further. – wally Oct 11 '17 at 20:38
  • It works fine for me if I try with an array of `char` or `int` rather than `MyChar` or if I preform a placement `new` one element at a time. – François Andrieux Oct 11 '17 at 20:45
  • Maybe the problem has something to do with placement new only being defined for [dynamic storage duration](https://stackoverflow.com/a/35395517/1460794)? – wally Oct 11 '17 at 20:55
  • 1
    @FrançoisAndrieux Weirdly enough, overwriting objects seems to be well-defined: [basic.life] 4 *"... For an object of a class type with a non-trivial destructor, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released; however, if there is no explicit call to the destructor or if a delete-expression (5.3.5) is not used to release the storage, the destructor shall not be implicitly called and any program that depends on the side effects produced by the destructor has undefined behavior."* – HolyBlackCat Oct 11 '17 at 20:59

1 Answers1

4

placement new of array may require more place than N * sizeof(Object)

(??? as compiler has to be able to call correctly the destructor with delete[] ???).

5.3.4 [expr.new]:

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

Here, x and y are non-negative unspecified values representing array allocation overhead; the result of the new-expression will be offset by this amount from the value returned by operator new[]. 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. The amount of overhead may vary from one invocation of new to another. —end example ]

Community
  • 1
  • 1
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • 2
    I'm not sure this applies to placement new. No memory is allocated. – François Andrieux Oct 11 '17 at 20:41
  • Then the solution is to call placement new for each element in a loop, right? – HolyBlackCat Oct 11 '17 at 20:46
  • @FrançoisAndrieux I believe [expr.new] permits that indirectly: *"If the entity is a non-array object, the new-expression returns a pointer to the object created. If it is an array, the new-expression returns a pointer to the initial element of the array."* – HolyBlackCat Oct 11 '17 at 20:48
  • @HolyBlackCat I don't see how the quote applies. Could you please elaborate? – François Andrieux Oct 11 '17 at 20:49
  • @françois: Whether or not memory is allocated, every element of the array needs to be destructed (since the type has a non-trivial destructor) and that requires that the number of elements be recorded. – rici Oct 11 '17 at 20:50
  • @rici That's a good point. I'll leave my original comment up so that others can have the full context. Does that imply that all arrays have non-trivial destructors? The problem persists even if `MyChar` is modified to be POD. – François Andrieux Oct 11 '17 at 20:51
  • @FrançoisAndrieux And the quote permits that number to be stored at the beginning of the passed memory block, thus offsetting the array. – HolyBlackCat Oct 11 '17 at 20:56
  • @HolyBlackCat shouldn't that be very easy to detect? `assert(p == new (p) T[10])`...? – sehe Oct 11 '17 at 20:59
  • @sehe It's indeed easy to detect, but I'm not sure if it's helpful. It seems easier to just avoid the placement new with arrays. – HolyBlackCat Oct 11 '17 at 21:00
  • It's helpful to move the answer from "constructive conjecturing" to "convincing explanation". I [completely agree](https://stackoverflow.com/questions/46682089/alocating-space-in-ram-then-creating-and-destroying-different-data-structures-h/46682335?noredirect=1#comment80331814_46682335) otherwise :) – sehe Oct 11 '17 at 21:01
  • 1
    I had no idea there was [a cookie involved](https://stackoverflow.com/a/8721932/1460794). – wally Oct 11 '17 at 21:09
  • @FrançoisAndrieux: It's compiler-dependent, I believe. – rici Oct 11 '17 at 21:18