Rozmieszczenie tablic - nowe wymaga nieokreślonego narzutu w buforze?

5.3.4 [expr.new] z projektu C++11 lut podaje przykład:

new(2,f) T[5] wyniki wywołania operator new[](sizeof(T)*5+y,2,f).

Tutaj, x i y są nieujemnymi, nieokreślonymi wartościami reprezentującymi overhead alokacji tablicy; wynik new-expression zostanie przesunięty o tę wartość od wartości zwracanej przez operator new[]. Ten narzut może być stosowany we wszystkich tablicach new-expressions , w tym odwołujących się do funkcji bibliotecznej operator new[](std::size_t, void*) i innych alokacji miejsc funkcje. Wysokość narzutu może się różnić w zależności od wywołania nowego. - end example ]

Weź teraz następujący przykładowy kod:

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

Zgodnie z powyższym cytatem, druga linia new (buffer) std::string[10] wewnętrznie wywoła operator new[](sizeof(std::string) * 10 + y, buffer) (przed zbudowaniem poszczególnych obiektów std::string). Problem polega na tym, że jeśli y > 0, wstępnie przydzielony bufor będzie zbyt mały!

Skąd mam wiedzieć, ile pamięci przydzielić przy użyciu tablicy lokata-Nowa?

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

Czy standard gdzieś gwarantuje, że y == 0 w tym przypadku? Ponownie cytat mówi:

Ten narzut może być stosowany we wszystkich tablicach new-expressions , w tym odwołujących się do funkcji bibliotecznej operator new[](std::size_t, void*) i innych funkcji alokacji miejsc.

Author: R. Martinho Fernandes, 2012-01-04

6 answers

Nie używaj operator new[](std::size_t, void* p) chyba, że znasz a-priori odpowiedź na to pytanie. Odpowiedź jest szczegółem implementacji i może się zmieniać wraz z kompilatorem / platformą. Chociaż jest zazwyczaj stabilny dla danej platformy. Np. jest to coś określonego przez Itanium ABI .

Jeśli nie znasz odpowiedzi na to pytanie, napisz własną tablicę umieszczającą new, która może to sprawdzić w czasie wykonywania:

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

Zmieniając rozmiar tablicy i sprawdzając n w powyższym przykładzie można wnioskować y dla Twojej platformy. Dla mojej platformy y jest 1 słowo. Rozmiar (word) różni się w zależności od tego, czy kompiluję dla architektury 32-bitowej, czy 64-bitowej.

 38
Author: Howard Hinnant,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-05 02:07:07

Update: po pewnej dyskusji rozumiem, że moja odpowiedź nie odnosi się już do pytania. Zostawię to tutaj, ale prawdziwa odpowiedź jest wciąż potrzebna.

Z przyjemnością poprę to pytanie jakąś nagrodą, jeśli wkrótce nie znajdziemy dobrej odpowiedzi.

Powtórzę pytanie tutaj, o ile je Rozumiem, mając nadzieję, że krótsza wersja może pomóc innym zrozumieć, co jest zadawane. Pytanie brzmi:

Ma następującą konstrukcję zawsze poprawne? Czy arr == addr na końcu?

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

Wiemy ze standardu, że #1 powoduje wywołanie ::operator new[](???, addr), Gdzie ??? jest nieokreśloną liczbą nie mniejszą niż N * sizeof(T), a także wiemy, że to wywołanie zwraca tylko addr i nie ma innych efektów. Wiemy również, że {[7] } jest odpowiednio przesunięta od addr. To, co robimy nie, to wiedzieć, czy pamięć wskazywana przez addr jest wystarczająco duża, lub skąd będziemy wiedzieć, ile pamięci przeznaczyć.


Wydaje się, że mylić kilka rzeczy:

  1. Twoje przykładowe wywołania operator new[](), nie operator new().

  2. Funkcje alokacji nie konstruują niczego. Oni przydzielają .

Dzieje się tak, że wyrażenie T * p = new T[10]; przyczyny:

  1. Wywołanie {[10] } z argumentem size10 * sizeof(T) + x,

  2. Dziesięć wywołań do domyślnego konstruktora T, efektywnie ::new (p + i) T().

Jedyny osobliwością jest to, że wyrażenie array-new prosi o więcej pamięci niż to, co jest używane przez same dane tablicy. Nie widzisz niczego z tego i nie możesz wykorzystać tych informacji w żaden inny sposób niż przez cichą akceptację.


Jeśli jesteś ciekaw, ile pamięci zostało faktycznie przydzielone, możesz po prostu zastąpić funkcje alokacji tablicy operator new[] i operator delete[] i wydrukować rzeczywisty rozmiar.


Update: jako przypadkowa informacja, należy pamiętać, że funkcje Global placement-new muszą być no-ops. To znaczy, gdy konstruujesz obiekt lub tablicę w miejscu w taki sposób:

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

Następnie odpowiednie wywołania do ::operator new(std::size_t, void*) i ::operator new[](std::size_t, void*) Nie robią nic poza zwracaniem drugiego argumentu. Jednak nie wiesz, na co buf10 ma wskazywać: musi wskazywać 10 * sizeof(T) + y bajty pamięci, ale nie możesz wiedzieć y.

 7
Author: Kerrek SB,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-02 13:08:52

Wywołanie dowolnej wersji operator new[] () nie będzie działać zbyt dobrze z obszarem pamięci o stałym rozmiarze. Zasadniczo zakłada się, że deleguje do jakiejś rzeczywistej funkcji alokacji pamięci, a nie tylko zwraca wskaźnik do przydzielonej pamięci. Jeśli masz już arenę pamięci, na której chcesz zbudować tablicę obiektów, użyj std::uninitialized_fill() lub std::uninitialized_copy() do konstruowania obiektów (lub innej formy indywidualnego konstruowania obiektów).

Można argumentować, że oznacza to, że trzeba zniszcz obiekty na arenie pamięci ręcznie, jak również. Jednak wywołanie delete[] array na wskaźniku zwróconym z miejsca umieszczenia new nie zadziała: użyje on wersji operator delete[] () bez miejsca umieszczenia! Oznacza to, że podczas używania lokacji new musisz ręcznie zniszczyć obiekt(y) i zwolnić pamięć.

 6
Author: Dietmar Kühl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-04 05:58:38

Jak wspomniał Kerrek SB w komentarzach, wada ta została po raz pierwszy zgłoszona w 2004 roku, a została rozwiązana w 2012 roku jako:

CWG zgodziło się, że EWG jest odpowiednim miejscem do rozwiązania tej kwestii.

Następnie wada została zgłoszona do EWG w 2013 roku, ale zamknięta jako NAD (prawdopodobnie oznacza "nie wada") z komentarzem:

Problem polega na próbie użycia array new do umieszczenia tablicy w istniejącej pamięci masowej. Nie musimy używać array new for that; just construct them.

Co prawdopodobnie oznacza, że sugerowane obejście polega na użyciu pętli z wywołaniem do nowego umieszczenia nie-tablicy dla każdego konstruowanego obiektu.


Następstwem nie wspomnianym nigdzie indziej w wątku jest to, że ten kod powoduje nieokreślone zachowanie dla wszystkich T:

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

Nawet jeśli przestrzegamy zasad życia (tzn. T albo ma trywialne zniszczenie, albo program nie zależy od destruktora skutki uboczne), problem polega na tym, że ptr został skorygowany dla tego nieokreślonego pliku cookie, więc jest to zła wartość, aby przejść do operator delete[].

 4
Author: M.M,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-14 22:39:42

Po przeczytaniu odpowiednich standardowych sekcji zaczynam myśleć, że umieszczenie nowego dla typów tablic jest po prostu bezużytecznym pomysłem, a jedynym powodem, dla którego jest dozwolone przez standard, jest ogólny sposób opisywania nowego operatora:

Nowe wyrażenie próbuje utworzyć obiekt o nazwie typeid (8.1) lub newtypeid, do którego jest stosowany. Typ tego obiektu to przypisany typ. Typ ten jest kompletnym typem obiektu, ale nie jest abstrakcyjny typ klasy lub array its (1.8, 3.9, 10.4). [Uwaga: Ponieważ referencje nie są obiektami, referencje nie mogą być tworzone przez newexpressions. ] [Uwaga: typeid może być typem cvqualified, w w którym przypadku obiekt utworzony przez newexpression ma CV Typ. ]

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

Wydaje mi się, że array placement new wynika po prostu ze zwartości definicji (wszystkie możliwe zastosowania jako jeden schemat) i wydaje się, że nie ma dobrego powodu, aby było to zabronione.

To pozostawia nas w sytuacji gdzie mamy bezużyteczny operator, który potrzebuje przydzielonej pamięci, zanim będzie wiadomo, ile z niej będzie potrzebne. Jedyne rozwiązania, które widzę, to albo nadalokacja pamięci i nadzieja, że kompilator nie będzie chciał więcej niż dostarczony, albo realokacja pamięci w nadrzędnej funkcji/metodzie array placement new (co raczej niszczy cel użycia array placement new w pierwszej kolejności).


Aby odpowiedzieć na pytanie zadane przez Kerrek SB: Twój przykład:

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

Nie zawsze jest poprawny. W większości implementacje arr!=addr (i są ku temu dobre powody) więc Twój kod nie jest poprawny, a Twój bufor zostanie przekroczony.

O tych "dobrych powodach" - zauważ, że jesteś zwolniony przez standardowych twórców z jakiegoś domu podczas korzystania z operatora array new i array placement new nie różni się pod tym względem. Zauważ, że nie musisz informować delete[] o długości tablicy, więc ta informacja musi być przechowywana w samej tablicy. Gdzie? Dokładnie w tej dodatkowej pamięci. Bez niego delete[]'ING wymagałby zachowanie długości tablicy oddzielnie (tak jak stl używa pętli i nie umieszczania new)

 1
Author: j_kubik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-04 22:54:54

Ten nagłówek może być stosowany we wszystkich tablicach new-expressions , w tym odwołujących się do funkcji bibliotecznej operator new[](std::size_t, void*) i innych funkcji alokacji miejsc.

Jest to wada normy. Plotka głosi, że nie mogli znaleźć ochotnika, który napisze wyjątek (wiadomość #1165).

Niewymienne umieszczenie tablicy-new nie może być używane z wyrażeniami delete[], więc musisz zapętlić przez array and call each destructor .

Narzut jest kierowany na ustawianie tablicy przez użytkownika-nowe funkcje, które przydzielają pamięć tak, jak zwykłe T* tp = new T[length]. Są one zgodne z delete[], stąd narzut, który nosi długość tablicy.
 0
Author: bit2shift,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-26 03:51:51