std :: वेक्टर (ab) स्वचालित भंडारण का उपयोग करता है


46

निम्नलिखित स्निपेट पर विचार करें:

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

जाहिर है कि यह अधिकांश प्लेटफार्मों पर दुर्घटनाग्रस्त होगा, क्योंकि डिफ़ॉल्ट स्टैक का आकार आमतौर पर 20 एमबी से कम होता है।

अब निम्नलिखित कोड पर विचार करें:

#include <array>
#include <vector>

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

हैरानी की बात यह है कि यह भी क्रैश! ट्रेसबैक (हाल के libstdc ++ संस्करणों में से एक के साथ) include/bits/stl_uninitialized.hफ़ाइल की ओर जाता है , जहां हम निम्नलिखित पंक्तियों को देख सकते हैं:

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

रीसाइज़िंग vectorकंस्ट्रक्टर को तत्वों को डिफ़ॉल्ट-इनिशियलाइज़ करना होगा और इसी तरह इसे लागू किया जाता है। जाहिर है, _ValueType()अस्थायी स्टैक को क्रैश करता है।

सवाल यह है कि क्या यह एक अनुरूप कार्यान्वयन है। यदि हाँ, तो वास्तव में इसका मतलब है कि विशाल प्रकार के वेक्टर का उपयोग काफी सीमित है, है ना?


एक सरणी प्रकार में बड़ी वस्तुओं को संग्रहित नहीं करना चाहिए। संभावित रूप से ऐसा करने के लिए संक्रामक स्मृति के एक बहुत बड़े क्षेत्र की आवश्यकता होती है जो मौजूद नहीं हो सकता है। इसके बजाय, पॉइंटर्स ऑफ़ वेक्टर (std :: unique_ptr) आमतौर पर रखें ताकि आप अपनी मेमोरी पर इतनी अधिक मांग न रखें।
नाथनऑलिवर

2
बस स्मृति है। C ++ कार्यान्वयन चल रहे हैं जो वर्चुअल मेमोरी का उपयोग नहीं करते हैं।
नाथनऑलिवर

3
कौन सा कंपाइलर, btw? मैं वीएस 2019 (16.4.2) के साथ पुन: पेश नहीं कर सकता
क्रिसएम

3
Libstdc ++ कोड को देखने से, यह कार्यान्वयन केवल तभी उपयोग किया जाता है यदि तत्व प्रकार तुच्छ और प्रतिलिपि असाइन करने योग्य है और यदि डिफ़ॉल्ट std::allocatorका उपयोग किया जाता है।
अखरोट

1
@Damon जैसा कि मैंने ऊपर उल्लेख किया है कि यह केवल डिफ़ॉल्ट आवंटनकर्ता के साथ तुच्छ प्रकारों के लिए उपयोग किया जाता है, इसलिए इसमें कोई भी अंतर नहीं होना चाहिए।
अखरोट

जवाबों:


19

किसी भी std API का उपयोग कितने स्वचालित भंडारण की कोई सीमा नहीं है।

वे सभी स्टैक स्पेस के 12 टेराबाइट्स की आवश्यकता कर सकते हैं।

हालाँकि, केवल API की आवश्यकता होती है Cpp17DefaultInsertable, और आपका कार्यान्वयन निर्माणकर्ता के लिए आवश्यक एक अतिरिक्त उदाहरण बनाता है। जब तक वस्तु का पता लगाने के पीछे यह तर्क नहीं दिया जाता है कि वह तुच्छ रूप से प्रमाणिक और प्रतिलिपि योग्य है, तो कार्यान्वयन अवैध दिखता है।


8
Libstdc ++ कोड को देखने से, यह कार्यान्वयन केवल तभी उपयोग किया जाता है यदि तत्व प्रकार तुच्छ और प्रतिलिपि असाइन करने योग्य है और यदि डिफ़ॉल्ट std::allocatorका उपयोग किया जाता है। मुझे यकीन नहीं है कि यह विशेष मामला पहले स्थान पर क्यों बना है।
अखरोट

3
@ क्वालन जिसका अर्थ है कि संकलक के रूप में स्वतंत्र है-अगर वास्तव में उस अस्थायी वस्तु का निर्माण नहीं होता है; मुझे लगता है कि एक अनुकूलित निर्माण पर एक अच्छा मौका है जो इसे बनाया नहीं जाता है?
यक्क - एडम नेवरामॉन्ट

4
हाँ, मुझे लगता है कि यह हो सकता है, लेकिन बड़े तत्वों के लिए GCC नहीं लगता है। Libstdc ++ के साथ क्लैंग अस्थायी रूप से ऑप्टिमाइज़ करता है, लेकिन ऐसा तभी लगता है जब कंस्ट्रक्टर को दिया गया वेक्टर आकार एक संकलन-समय स्थिर हो, Godbolt.org/z/-2ZDMm देखें ।
अखरोट

1
@wnut विशेष मामला यह है कि हम std::fillतुच्छ प्रकारों के लिए प्रेषण करते हैं, जो तब memcpyबाइट्स को स्थानों में विस्फोट करने के लिए उपयोग करता है, जो कि लूप में व्यक्तिगत वस्तुओं के बहुत सारे निर्माण की तुलना में संभवतः अधिक तेज है। मेरा मानना ​​है कि libstdc ++ कार्यान्वयन अनुरूप है, लेकिन विशाल वस्तुओं के लिए स्टैक ओवरफ़्लो का कारण कार्यान्वयन (क्यूओआई) बग की एक गुणवत्ता है। मैंने इसे gcc.gnu.org/PR94540 के रूप में रिपोर्ट किया है और इसे ठीक कर दूंगा।
जोनाथन वेकली

@JonathanWakely हाँ, यह समझ में आता है। मुझे याद नहीं है कि जब मैंने अपनी टिप्पणी लिखी थी तब मैंने ऐसा क्यों नहीं सोचा। मुझे लगता है कि मैंने सोचा होगा कि पहले डिफ़ॉल्ट-निर्मित तत्व का निर्माण सीधे इन-प्लेस में किया जाएगा और फिर कोई भी इससे कॉपी कर सकता है, जिससे कि तत्व प्रकार की कोई अतिरिक्त ऑब्जेक्ट कभी भी नहीं बनाई जाएगी। लेकिन निश्चित रूप से मैंने इसके बारे में विस्तार से नहीं सोचा है और मुझे मानक पुस्तकालय को लागू करने के बारे में पता नहीं है। (मुझे बहुत देर से पता चला कि बग रिपोर्ट में आपका सुझाव भी है।)
अखरोट

9
huge_type t;

जाहिर है कि यह अधिकांश प्लेटफार्मों पर दुर्घटनाग्रस्त होगा ...

मैं "सबसे" की धारणा का विवाद करता हूं। चूंकि विशाल ऑब्जेक्ट की मेमोरी का उपयोग कभी नहीं किया जाता है, कंपाइलर इसे पूरी तरह से अनदेखा कर सकता है और कभी भी उस मेमोरी को आवंटित नहीं कर सकता है जिस स्थिति में कोई दुर्घटना नहीं होगी।

सवाल यह है कि क्या यह एक अनुरूप कार्यान्वयन है।

C ++ मानक स्टैक उपयोग को सीमित नहीं करता है, या स्टैक के अस्तित्व को भी स्वीकार करता है। तो, हाँ यह मानक के अनुरूप है। लेकिन कोई इसे कार्यान्वयन के मुद्दे की गुणवत्ता के रूप में मान सकता है।

इसका वास्तव में मतलब है कि विशाल प्रकार के वेक्टर का उपयोग काफी सीमित है, है ना?

यह libstdc ++ के मामले में प्रतीत होता है। दुर्घटना को libc ++ (क्लैंग का उपयोग करके) के साथ पुन: प्रस्तुत नहीं किया गया था, इसलिए ऐसा लगता है कि यह भाषा में सीमा नहीं है, बल्कि केवल उस विशेष कार्यान्वयन में है।


6
"स्टैक के अतिप्रवाह के बावजूद आवश्यक रूप से दुर्घटना नहीं होगी क्योंकि आवंटित मेमोरी प्रोग्राम द्वारा कभी एक्सेस नहीं की जाती है" - यदि इसके बाद किसी भी तरह से स्टैक का उपयोग किया जाता है (जैसे किसी फ़ंक्शन को कॉल करने के लिए), तो यह ओवर-कमिंग प्लेटफ़ॉर्म पर भी क्रैश हो जाएगा ।
रुस्लान

कोई भी प्लेटफ़ॉर्म जिस पर यह क्रैश नहीं होता है (ऑब्जेक्ट को सफलतापूर्वक आवंटित नहीं किया गया है) स्टैक क्लैश की चपेट में है।
user253751

@ user253751 यह मान लेना आशावादी होगा कि अधिकांश मंच / कार्यक्रम असुरक्षित नहीं हैं।
एरोरिका

मुझे लगता है कि ओवरकमिट केवल ढेर पर लागू होता है, स्टैक पर नहीं। स्टैक के आकार पर एक निश्चित ऊपरी सीमा होती है।
जोनाथन वेकली

@JonathanWakely तुम सही हो। ऐसा प्रतीत होता है कि इसका कारण दुर्घटना नहीं है क्योंकि संकलक कभी भी अप्रयुक्त वस्तु को आवंटित नहीं करता है।
एरोरिका

5

मैं भाषा का वकील नहीं हूं और न ही C ++ मानक विशेषज्ञ, लेकिन cppreference.com कहता है:

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

टी। की कोई भी प्रतिलिपि नहीं बनाई गई डिफ़ॉल्ट रूप से सम्मिलित उदाहरणों के साथ कंटेनर का निर्माण करता है।

शायद मैं "डिफ़ॉल्ट-सम्मिलित" गलत समझ रहा हूं, लेकिन मुझे उम्मीद है:

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

के बराबर होना

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

बाद वाले संस्करण को स्टैक कॉपी नहीं बनाना चाहिए, लेकिन वेक्टर की डायनेमिक मेमोरी में सीधे एक विशाल_टाइप का निर्माण करें।

मैं आधिकारिक तौर पर यह नहीं कह सकता कि जो आप देख रहे हैं वह गैर-अनुपालन है, लेकिन यह निश्चित रूप से वह नहीं है जो मैं एक गुणवत्ता कार्यान्वयन से उम्मीद करूंगा।


4
जैसा कि मैंने प्रश्न पर एक टिप्पणी में उल्लेख किया है, libstdc ++ केवल कॉपी असाइनमेंट के साथ तुच्छ प्रकारों के लिए इस कार्यान्वयन का उपयोग करता है std::allocator, और इसलिए सीधे वैक्टर मेमोरी में डालने और एक मध्यवर्ती प्रतिलिपि बनाने के बीच कोई अवलोकन योग्य अंतर नहीं होना चाहिए।
अखरोट

@walnut: सही है, लेकिन भारी स्टैक आवंटन और init और कॉपी के प्रदर्शन प्रभाव अभी भी ऐसी चीजें हैं जो मैं एक उच्च गुणवत्ता वाले कार्यान्वयन से उम्मीद नहीं करूंगा।
एड्रियन मैकार्थी

2
हाँ मैं सहमत हूँ। मुझे लगता है कि यह कार्यान्वयन में एक निरीक्षण था। मेरा कहना केवल इतना था कि यह मानक अनुपालन के मामले में मायने नहीं रखता है।
अखरोट

IIRC आपको emplace_backकेवल एक वेक्टर बनाने के लिए नहीं, बल्कि इसके लिए भी कॉपी करने या अक्षमता की आवश्यकता है । जिसका अर्थ है कि आपके पास हो सकता है vector<mutex> v(1)लेकिन vector<mutex> v; v.emplace_back();कुछ के लिए नहीं जैसे huge_typeआपके पास अभी भी एक आवंटन हो सकता है और दूसरे संस्करण के साथ ऑपरेशन को और अधिक बढ़ा सकता है। न ही अस्थायी वस्तुओं का निर्माण करना चाहिए।
DYP

1
@IgorR। vector::vector(size_type, Allocator const&)आवश्यकता होती है (Cpp17) DefaultInsertable
dyp
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.