std :: vector (ab) otomatik depolama kullanır


46

Aşağıdaki snippet'i düşünün:

#include <array>
int main() {
  using huge_type = std::array<char, 20*1024*1024>;
  huge_type t;
}

Açıktır ki, varsayılan yığın boyutu genellikle 20 MB'den küçük olduğundan, çoğu platformda çökecektir.

Şimdi aşağıdaki kodu göz önünde bulundurun:

#include <array>
#include <vector>

int main() {
  using huge_type = std::array<char, 20*1024*1024>;
  std::vector<huge_type> v(1);
}

Şaşırtıcı bir şekilde de çöküyor! Geri izleme (son libstdc ++ sürümlerinden biriyle) include/bits/stl_uninitialized.h, aşağıdaki satırları görebileceğimiz dosyaya yol açar :

typedef typename iterator_traits<_ForwardIterator>::value_type _ValueType;
std::fill(__first, __last, _ValueType());

Yeniden boyutlandırma vectorkurucusu öğeleri varsayılan olarak başlatmalıdır ve bu şekilde uygulanır. Belli ki, _ValueType()yığın yığını çöküyor.

Soru, bunun uygun bir uygulama olup olmadığıdır. Evetse, aslında büyük tipte bir vektörün kullanımının oldukça sınırlı olduğu anlamına gelir, değil mi?


Bir dizi türünde büyük nesneler depolanmamalıdır. Bunu yapmak potansiyel olarak mevcut olmayan çok geniş bir kalıcı bellek bölgesini gerektirir. Bunun yerine, belleğinize bu kadar yüksek bir talepte bulunmamanız için bir işaretçi vektörü (genellikle std :: unique_ptr) kullanın.
NathanOliver

2
Sadece hafıza. Sanal bellek kullanmayan C ++ uygulamaları var.
NathanOliver

3
Hangi derleyici, btw? VS 2019 ile çoğaltamıyorum (16.4.2)
ChrisMM

3
Libstdc ++ koduna bakıldığında, bu uygulama yalnızca öğe türü önemsizse ve kopya atanabilirse ve varsayılan std::allocatordeğer kullanılırsa kullanılır.
Ceviz

1
@Damon Yukarıda bahsettiğim gibi, sadece varsayılan ayırıcılı önemsiz türler için kullanılıyor gibi görünüyor, bu yüzden gözlemlenebilir bir fark olmamalıdır.
Ceviz

Yanıtlar:


19

Herhangi bir std API'nin ne kadar otomatik depolama alanı kullandığı konusunda herhangi bir sınırlama yoktur.

Hepsi 12 terabayt yığın alanı gerektirebilir.

Ancak, bu API yalnızca gerektirir Cpp17DefaultInsertableve uygulamanız yapıcı tarafından gerekenler üzerine fazladan bir örnek oluşturur. Nesnenin önemsiz bir şekilde saklanıp kopyalanabileceğini tespit etmedikçe, uygulama yasa dışı görünüyor.


8
Libstdc ++ koduna bakıldığında, bu uygulama yalnızca öğe türü önemsizse ve kopya atanabilirse ve varsayılan std::allocatordeğer kullanılırsa kullanılır. İlk başta neden bu özel davanın yapıldığından emin değilim.
Ceviz

3
@walnut Yani derleyici aslında bu geçici nesneyi yaratmazsa özgürdür; Ben optimize edilmemiş bir yapı üzerinde iyi bir şans var tahmin değil mi?
Yakk - Adam Nevraumont

4
Evet, sanırım olabilir, ancak büyük elementler için GCC görünmüyor. Libstdc ++ ile Clang geçici olanı optimize eder, ancak sadece yapıcıya iletilen vektör boyutu derleme zamanı sabiti ise, bkz. Godbolt.org/z/-2ZDMm .
Ceviz

1
@walnut, özel durum oradadır std::fill, böylece daha sonra memcpybaytları yerlere vurmak için kullanılan önemsiz türler için göndeririz , bu da bir döngüde çok sayıda tek tek nesne oluşturmaktan çok daha hızlıdır. Libstdc ++ uygulamasının uygun olduğuna inanıyorum, ancak büyük nesneler için yığın taşmasına neden olan bir Uygulama Kalitesi (QoI) hatası. Bunu gcc.gnu.org/PR94540 olarak bildirdim ve düzeltirim.
Jonathan Wakely

@JonathanWakely Evet, bu mantıklı. Yorumumu yazarken neden böyle düşünmediğimi hatırlamıyorum. İlk varsayılan olarak oluşturulmuş öğenin doğrudan yerinde oluşturulacağını ve daha sonra bu öğeden kopyalanabileceğini düşünürdüm, böylece öğe türünün hiçbir ek nesnesi oluşturulamaz. Ama elbette bunu detaylı olarak düşünmedim ve standart kütüphaneyi uygulamanın giriş ve çıkışlarını bilmiyorum. (Bunun hata raporundaki öneriniz olduğunu çok geç farkettim.)
ceviz

9
huge_type t;

Açıkçası platformların çoğunda çökecekti ...

"En" varsayımına itiraz ediyorum. Büyük nesnenin hafızası asla kullanılmadığından, derleyici onu tamamen görmezden gelebilir ve hafızayı asla tahsis edemez, bu durumda çökme olmaz.

Soru, bunun uygun bir uygulama olup olmadığıdır.

C ++ standardı yığın kullanımını sınırlamaz, hatta bir yığının varlığını bile kabul etmez. Yani, evet standarda uygundur. Ancak, bunun uygulama kalitesi sorunu olduğu düşünülebilir.

aslında büyük tipte bir vektörün kullanımının oldukça sınırlı olduğu anlamına gelir, değil mi?

Bu libstdc ++ ile böyle görünüyor. Çökme libc ++ ile üretilmedi (clang kullanarak), bu yüzden bu dilde bir sınırlama değil, sadece belirli bir uygulamada görünüyor.


6
"yığının taşmasına rağmen zorunlu olarak çökmeyecektir, çünkü ayrılan belleğe hiçbir zaman program tarafından erişilmiyor" - yığın bundan sonra herhangi bir şekilde kullanılırsa (örneğin bir işlevi çağırmak için), aşırı işleyen platformlarda bile çökecektir .
Ruslan

Bunun çökmediği herhangi bir platform (nesnenin başarıyla ayrılmadığı varsayılarak) Stack Clash'a karşı savunmasızdır.
user253751

@ user253751 Çoğu platformun / programın savunmasız olmadığını varsaymak iyimser olacaktır.
eerorika

Bence aşırı hizmet yığını için değil, sadece yığın için geçerlidir. Yığının boyutunda sabit bir üst sınırı vardır.
Jonathan Wakely

@JonathanWakely Haklısın. Görünüşe göre, çökmemesinin nedeni, derleyicinin kullanılmayan nesneyi asla ayırmamasıdır.
eerorika

5

Ben bir dil avukatı veya C ++ standart uzmanı değilim, ama cppreference.com diyor ki:

explicit vector( size_type count, const Allocator& alloc = Allocator() );

Kapsayıcıyı varsayılan olarak eklenen T örnekleriyle oluşturur. Kopya yapılmaz.

Belki "varsayılan takılı" yanlış anlıyorum, ama beklenir:

std::vector<huge_type> v(1);

eşdeğer olmak

std::vector<huge_type> v;
v.emplace_back();

İkinci sürüm bir yığın kopya oluşturmamalı, doğrudan vektörün dinamik belleğinde büyük bir tür oluşturmalıdır.

Yetkili olarak gördüğünüz şeyin uyumlu olmadığını söyleyemem, ancak kaliteli bir uygulamadan kesinlikle beklediğim şey bu değil.


4
Soruyla ilgili bir yorumda belirttiğim gibi, libstdc ++ bu uygulamayı yalnızca kopya atama içeren önemsiz türler için kullanır ve bu std::allocatornedenle doğrudan vektör belleğine ekleme ve ara kopya oluşturma arasında gözlemlenebilir bir fark olmamalıdır.
Ceviz

@walnut: Doğru, ancak büyük yığın tahsisi ve init ve kopyanın performans etkisi hala yüksek kaliteli bir uygulamadan beklemediğim şeyler.
Adrian McCarthy

2
Evet katılıyorum. Bunun uygulamada bir gözetim olduğunu düşünüyorum. Demek istediğim sadece standart uygunluk açısından önemi yoktu.
Ceviz

IIRC ayrıca emplace_backsadece bir vektör oluşturmak için değil, kopyalanabilirlik veya taşınabilirliğe ihtiyacınız vardır . Bu, sahip olabileceğiniz vector<mutex> v(1)ama olmayabileceğiniz anlamına gelir. vector<mutex> v; v.emplace_back();Gibi bir şey için huge_typehala ikinci sürümle bir tahsis ve taşıma işlemi daha olabilir. İkisi de geçici nesneler yaratmamalıdır.
dyp

1
@IgorR. vector::vector(size_type, Allocator const&)gerektirir (Cpp17) DefaultInsertable
dyp
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.