Rozważ następujący fragment kodu:
#include <array>
int main() {
using huge_type = std::array<char, 20*1024*1024>;
huge_type t;
}
Oczywiście ulegnie awarii na większości platform, ponieważ domyślny rozmiar stosu jest zwykle mniejszy niż 20 MB.
Teraz rozważ następujący kod:
#include <array>
#include <vector>
int main() {
using huge_type = std::array<char, 20*1024*1024>;
std::vector<huge_type> v(1);
}
Co zaskakujące, również się zawiesza! Tracback (z jedną z najnowszych wersji libstdc ++) prowadzi do include/bits/stl_uninitialized.h
pliku, w którym możemy zobaczyć następujące linie:
typedef typename iterator_traits<_ForwardIterator>::value_type _ValueType;
std::fill(__first, __last, _ValueType());
Konstruktor zmiany rozmiaru vector
musi domyślnie zainicjować elementy i w ten sposób jest implementowany. Oczywiście _ValueType()
tymczasowe awarie stosu.
Pytanie brzmi, czy jest to zgodna implementacja. Jeśli tak, to w rzeczywistości oznacza to, że użycie wektora ogromnych typów jest dość ograniczone, prawda?
std::allocator
używana jest wartość domyślna .Odpowiedzi:
Nie ma ograniczenia, ile automatycznej pamięci używa każdy interfejs std API.
Wszystkie mogą wymagać 12 terabajtów miejsca na stosie.
Jednak ten interfejs API wymaga tylko
Cpp17DefaultInsertable
, a twoja implementacja tworzy dodatkową instancję w stosunku do wymagań konstruktora. O ile implementacja nie jest ukryta za wykrywaniem obiektu, który można w trywialny sposób kopiować i kopiować, implementacja wygląda nielegalnie.źródło
std::allocator
używana jest wartość domyślna . Nie jestem pewien, dlaczego ten szczególny przypadek został stworzony.std::fill
trywialnych typów, które następnie używająmemcpy
do wysadzania bajtów w miejsca, co jest potencjalnie znacznie szybsze niż konstruowanie wielu pojedynczych obiektów w pętli. Wierzę, że implementacja libstdc ++ jest zgodna, ale powodowanie przepełnienia stosu dla dużych obiektów jest błędem jakości implementacji (QoI). Zgłosiłem to jako gcc.gnu.org/PR94540 i naprawię to.Kwestionuję założenie „większości”. Ponieważ pamięć dużego obiektu nigdy nie jest używana, kompilator może go całkowicie zignorować i nigdy nie przydzielać pamięci, w którym to przypadku nie nastąpiłby awaria.
Standard C ++ nie ogranicza użycia stosu, a nawet potwierdza istnienie stosu. Tak, to jest zgodne ze standardem. Można jednak uznać to za kwestię jakości wdrożenia.
Tak wydaje się być w przypadku libstdc ++. Katastrofa nie została odtworzona w libc ++ (przy użyciu clang), więc wydaje się, że nie jest to ograniczenie w języku, ale tylko w tej konkretnej implementacji.
źródło
Nie jestem prawnikiem językowym ani ekspertem od standardu C ++, ale cppreference.com mówi:
Być może źle rozumiem „domyślnie wstawiony”, ale oczekiwałbym:
być równoważnym z
Ta ostatnia wersja nie powinna tworzyć kopii stosu, ale konstruować ogromny typ bezpośrednio w pamięci dynamicznej wektora.
Nie mogę autorytatywnie powiedzieć, że to, co widzisz, jest niezgodne, ale z pewnością nie jest to, czego oczekiwałbym od wysokiej jakości wdrożenia.
źródło
std::allocator
, więc nie powinno być zauważalnej różnicy między wstawieniem bezpośrednio do pamięci wektorów a utworzeniem kopii pośredniej.emplace_back
ale nie tylko do tworzenia wektora. Co oznacza, że możesz mieć,vector<mutex> v(1)
ale nie możesz.vector<mutex> v; v.emplace_back();
W przypadku czegoś takiegohuge_type
nadal możesz mieć przydział i przenieść operację więcej w drugiej wersji. Żadne nie powinno tworzyć obiektów tymczasowych.vector::vector(size_type, Allocator const&)
wymaga (Cpp17) DefaultInsertable