कैसे हटाएं [] पता है कि यह एक सरणी है?


136

ठीक है, मुझे लगता है कि हम सभी सहमत हैं कि निम्नलिखित कोड के साथ क्या होता है अपरिभाषित है, जो पारित हुआ है उसके आधार पर,

void deleteForMe(int* pointer)
{
     delete[] pointer;
}

सूचक सभी प्रकार की अलग-अलग चीजें हो सकती हैं, और इसलिए delete[]उस पर बिना शर्त प्रदर्शन करना अपरिभाषित है। हालाँकि, मान लें कि हम वास्तव में एक सरणी पॉइंटर पास कर रहे हैं,

int main()
{
     int* arr = new int[5];
     deleteForMe(arr);
     return 0;
}

मेरा प्रश्न इस मामले जहां सूचक में, है है एक सरणी है, जो यह है कि इस जानता है? मेरा मतलब है, भाषा / संकलक के दृष्टिकोण से, इसका कोई विचार नहीं है कि arrएक सरणी सूचक बनाम एक एकल इंट करने के लिए एक सूचक है या नहीं । हेक, यह भी नहीं पता है कि क्या arrगतिशील रूप से बनाया गया था। फिर भी, अगर मैं इसके बजाय निम्नलिखित करता हूं,

int main()
{
     int* num = new int(1);
     deleteForMe(num);
     return 0;
}

ओएस केवल एक इंट को हटाने के लिए पर्याप्त स्मार्ट है और उस बिंदु से परे शेष मेमोरी को हटाकर किसी प्रकार की 'हत्या की होड़' पर नहीं जाता है (इसके विपरीत strlenऔर गैर \0-स्ट्रिंग स्ट्रिंग के साथ - यह तब तक चलता रहेगा। हिट 0)।

तो इन कामों को याद रखना किसका काम है? क्या OS पृष्ठभूमि में कुछ प्रकार का रिकॉर्ड रखता है? (मेरा मतलब है, मुझे एहसास है कि मैंने यह कहकर इस पद की शुरुआत की कि क्या होता है अपरिभाषित है, लेकिन तथ्य यह है कि, 'हत्या की होड़' परिदृश्य ऐसा नहीं होता है, इसलिए इसलिए व्यावहारिक दुनिया में कोई याद कर रहा है।)



6
यह हटाए जाने के बाद वर्ग कोष्ठक से जानता है
जोएलफ़न

"सूचक एक सरणी है"। नहीं, संकेत कभी सरणियाँ नहीं हैं। वे अक्सर सरणी के पहले तत्व को इंगित करते हैं, लेकिन यह एक अलग बात है।
आरोन मैकडैड

जवाबों:


99

संकलक को यह पता नहीं है कि यह एक सरणी है, यह प्रोग्रामर पर भरोसा कर रहा है। एकल के intसाथ एक पॉइंटर को हटाने delete []से अपरिभाषित व्यवहार होता है। आपका दूसरा main()उदाहरण असुरक्षित है, भले ही वह तुरंत दुर्घटनाग्रस्त न हो।

कंपाइलर को इस बात का ध्यान रखना होगा कि कितनी वस्तुओं को किसी तरह हटाने की आवश्यकता है। यह सरणी आकार को संग्रहीत करने के लिए पर्याप्त रूप से अधिक-आवंटन करके ऐसा कर सकता है। अधिक जानकारी के लिए, C ++ सुपर FAQ देखें


14
दरअसल, नए के साथ बनाई गई चीज़ को हटाने के लिए डिलीट [] का उपयोग करना शोषक है। taossa.com/index.php/2007/01/03/…
रोड्रिगो

23
@ रोड्रिगो आपकी टिप्पणी की लिंक टूटी हुई है, लेकिन शुक्र है कि वेपबैक मशीन की एक प्रति replay.web.archive.org/20080703153358/http://taossa.com/… पर है
डेविड

103

एक सवाल जो अब तक दिए गए उत्तरों से पता नहीं लगता है: यदि रनटाइम लाइब्रेरीज़ (ओएस नहीं, वास्तव में) सरणी में चीजों की संख्या पर नज़र रख सकती हैं, तो हमें delete[]सिंटैक्स की आवश्यकता क्यों है ? deleteसभी डिलीट को हैंडल करने के लिए एक ही फॉर्म का उपयोग क्यों नहीं किया जा सकता है?

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

यही है, अगर आपका कोड बस करता है

Foo* foo = new Foo;

इसके बाद जो मेमोरी स्पेस आवंटित किया जाता है, उसमें fooकिसी भी अतिरिक्त ओवरहेड को शामिल नहीं किया जाना चाहिए, जो कि सरणियों का समर्थन करने के लिए आवश्यक होगाFoo

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

delete[] bar;

इसके बजाय बस

delete bar;

यदि बार किसी व्यूह का सूचक है।

हम में से अधिकांश के लिए (खुद को शामिल), कि स्मृति के कुछ अतिरिक्त बाइट्स के बारे में उपद्रव इन दिनों विचित्र लगता है। लेकिन अभी भी कुछ परिस्थितियाँ हैं जहाँ कुछ बाइट्स (जो बहुत अधिक संख्या में मेमोरी ब्लॉक हो सकते हैं) को सहेजना महत्वपूर्ण हो सकता है।


20
"स्मृति के कुछ अतिरिक्त बाइट्स के बारे में घबराहट इन दिनों विचित्र लगती है"। सौभाग्य से, ऐसे लोगों को नंगे सरण भी विचित्र लगने लगे हैं, इसलिए वे सिर्फ एक वेक्टर या बढ़ावा :: सरणी का उपयोग कर सकते हैं, और हमेशा के लिए हटाने के बारे में भूल जाते हैं [] हमेशा के लिए :-)
स्टीव जेसप

28

हां, ओएस कुछ चीजें 'पृष्ठभूमि' में रखता है। उदाहरण के लिए, यदि आप चलाते हैं

int* num = new int[5];

ओएस 4 अतिरिक्त बाइट्स आवंटित कर सकता है, आवंटित मेमोरी के पहले 4 बाइट्स में आवंटन के आकार को स्टोर कर सकता है और एक ऑफसेट पॉइंटर लौटाता है (यानी, यह 1000 से 1024 तक मेमोरी स्पेस आवंटित करता है, लेकिन पॉइंटर ने 1004 पर अंक दिए हैं, जिसमें लोकेशन 1000- हैं 1003 आबंटन का आकार)। फिर, जब हटाने को कहा जाता है, तो यह आवंटन के आकार को खोजने के लिए पॉइंटर को पास करने से पहले 4 बाइट्स को देख सकता है।

मुझे यकीन है कि आवंटन के आकार को ट्रैक करने के अन्य तरीके हैं, लेकिन यह एक विकल्प है।


26
+1 - सामान्य रूप से मान्य बिंदु, सिवाय इसके कि आमतौर पर भाषा रनटाइम इस मेटाडेटा के भंडारण के लिए जिम्मेदार है, ओएस नहीं।
शार्पट्यूट

किसी ऑब्जेक्ट के सरणी या आकार के आकार का क्या होता है जिसमें सरणी परिभाषित होती है? जब आप उस ऑब्जेक्ट पर एक आकार का करते हैं तो क्या यह अतिरिक्त 4 बाइट्स दिखाता है?
श्री

3
नहीं, sizeof सिर्फ सरणी के आकार को दिखाता है। यदि रनटाइम इसे w / लागू करने के लिए चुनता है, तो मैंने जो विधि बताई है, वह कड़ाई से कार्यान्वयन का विवरण है और उपयोगकर्ता के दृष्टिकोण से, जिसे मास्क किया जाना चाहिए। पॉइंटर से पहले की स्मृति उपयोगकर्ता के लिए 'संबंधित' नहीं होती है, और गिना नहीं जाता है।
bsdfish

2
इससे भी महत्वपूर्ण बात, आकार किसी भी मामले में एक गतिशील रूप से आवंटित सरणी का सही आकार नहीं लौटाएगा। यह केवल संकलन समय पर ज्ञात आकार वापस कर सकता है।

सरणी पर सटीक लूप के लिए लूप के लिए इस मेटाडेटा का उपयोग करना संभव है? जैसे for(int i = 0; i < *(arrayPointer - 1); i++){ }
सैम

13

यह इस प्रश्न से बहुत मिलता-जुलता है और इसमें आपके द्वारा खोजे जा रहे कई विवरण हैं।

लेकिन कहने के लिए पर्याप्त है, यह किसी भी ट्रैक करने के लिए ओएस का काम नहीं है। यह वास्तव में रनटाइम लाइब्रेरी या अंतर्निहित मेमोरी मैनेजर है जो सरणी के आकार को ट्रैक करेगा। यह आमतौर पर सामने की ओर अतिरिक्त मेमोरी को आवंटित करने और उस स्थान में सरणी के आकार को संग्रहीत करने के द्वारा किया जाता है (ज्यादातर एक हेड नोड का उपयोग करें)।

यह निम्नलिखित कोड को निष्पादित करके कुछ कार्यान्वयन पर देखा जा सकता है

int* pArray = new int[5];
int size = *(pArray-1);

यह काम करेगा? विंडोज़ और लिनक्स में हमने यह काम नहीं किया।
दोस्त

1
size_t size = *(reinterpret_cast<size_t *>(pArray) - 1)इसके बजाय कोशिश करें

9

deleteया delete[]संभवत: आवंटित मेमोरी (मेमोरी पॉइंट) दोनों को मुक्त कर देगा, लेकिन बड़ा अंतर यह हैdelete सरणी पर प्रत्येक तत्व के विनाशकर्ता को कॉल नहीं किया जाएगा।

वैसे भी, मिश्रण new/new[]और delete/delete[]शायद यूबी है।


1
स्पष्ट, छोटा और सबसे उपयोगी उत्तर!
GntS

6

यह नहीं पता है कि यह एक सरणी है, इसलिए आपको delete[]नियमित पुराने के बजाय आपूर्ति करनी होगी delete


5

मेरा भी इसी तरह का सवाल था। सी में, आप मैलोकोक () या (अन्य समान फ़ंक्शन) के साथ मेमोरी आवंटित करते हैं, और इसे मुफ्त () के साथ हटाते हैं। केवल एक मॉलोक () है, जो बस एक निश्चित संख्या में बाइट्स आवंटित करता है। केवल एक फ्री () है, जो एक पॉइंटर को पैरामीटर के रूप में लेता है।

तो ऐसा क्यों है कि सी में आप सिर्फ पॉइंटर को मुफ्त में सौंप सकते हैं, लेकिन सी ++ में आपको यह बताना होगा कि यह एक सरणी है या एक एकल चर है?

जवाब, मैंने सीखा है, वर्ग के विनाशकों के साथ क्या करना है।

यदि आप एक वर्ग MyClass का एक उदाहरण आवंटित करते हैं ...

classes = new MyClass[3];

और इसे डिलीट करने के साथ हटा दें, आपको केवल MyClass नामक पहली बार के लिए विध्वंसक मिल सकता है। यदि आप डिलीट [] का उपयोग करते हैं, तो आपको आश्वासन दिया जा सकता है कि विनाशकर्ता को सरणी में सभी उदाहरणों के लिए बुलाया जाएगा।

यह महत्वपूर्ण अंतर है। यदि आप बस मानक प्रकार (जैसे int) के साथ काम कर रहे हैं तो आप वास्तव में इस मुद्दे को नहीं देखेंगे। साथ ही, आपको याद रखना चाहिए कि नए पर डिलीट का उपयोग करने के लिए व्यवहार [] और नए पर डिलीट [] अपरिभाषित है - यह हर कंपाइलर / सिस्टम पर उसी तरह काम नहीं कर सकता है।


3

यह रनटाइम तक है जो मेमोरी आवंटन के लिए जिम्मेदार है, उसी तरह से आप मानक का उपयोग करके बनाए गए सरणी को हटा सकते हैं। मुझे लगता है कि प्रत्येक कंपाइलर इसे अलग तरीके से लागू करता है। एक सामान्य तरीका है, सरणी आकार के लिए एक अतिरिक्त सेल आवंटित करना।

हालांकि, रनटाइम यह पता लगाने के लिए पर्याप्त स्मार्ट नहीं है कि यह एक सरणी या पॉइंटर है या नहीं, आपको इसकी सूचना देनी होगी, और यदि आपसे गलती हुई है, तो आप या तो सही तरीके से नहीं हटाते हैं (जैसे, एरे के बजाय ptr), या आप आकार के लिए एक असंबंधित मूल्य लेते हैं और महत्वपूर्ण क्षति का कारण बनते हैं।


3

संकलक के लिए दृष्टिकोणों में से एक थोड़ा अधिक स्मृति आवंटित करना और सिर तत्व में तत्वों की गिनती करना है।

उदाहरण यह है कि यह कैसे किया जा सकता है: यहाँ

int* i = new int[4];

संकलक आकार (अंतर) * 5 बाइट्स आवंटित करेगा।

int *temp = malloc(sizeof(int)*5)

4पहले sizeof(int)बाइट्स में स्टोर करेगा

*temp = 4;

और सेट करें i

i = temp + 1;

तो i5 नहीं, 4 तत्वों की सरणी को इंगित करता है।

तथा

delete[] i;

निम्नलिखित तरीके से संसाधित किया जाएगा

int *temp = i - 1;
int numbers_of_element = *temp; // = 4
... call destructor for numbers_of_element elements if needed
... that are stored in temp + 1, temp + 2, ... temp + 4
free (temp)

1

शब्दार्थ, C ++ में डिलीट ऑपरेटर के दोनों संस्करण किसी भी पॉइंटर को "खा" सकते हैं; हालाँकि, यदि किसी एकल वस्तु के लिए एक संकेतक दिया जाता हैdelete[] , तो यूबी परिणाम देगा, मतलब कुछ भी हो सकता है, जिसमें सिस्टम क्रैश या कुछ भी शामिल नहीं है।

C ++ को प्रोग्रामर को डीललोकेशन के विषय के आधार पर डिलीट ऑपरेटर के उचित संस्करण को चुनने की आवश्यकता है: सरणी या एकल ऑब्जेक्ट।

यदि कंपाइलर स्वचालित रूप से यह निर्धारित कर सकता है कि डिलीट ऑपरेटर को दिया गया पॉइंटर एक पॉइंटर ऐरे है, तो C ++ में केवल एक डिलीट ऑपरेटर होगा, जो दोनों मामलों के लिए पर्याप्त होगा।


1

सहमत हूँ कि संकलक को पता नहीं है कि यह एक सरणी है या नहीं। यह प्रोग्रामर पर निर्भर है।

कंपाइलर कभी-कभी सरणी आकार को स्टोर करने के लिए पर्याप्त-से-अधिक आवंटित करके कितनी वस्तुओं को हटाने की आवश्यकता है, इसका ट्रैक रखता है, लेकिन हमेशा आवश्यक नहीं होता है।

एक पूर्ण विनिर्देशन के लिए जब अतिरिक्त भंडारण आवंटित किया जाता है, तो कृपया C ++ ABI (कंपाइलर कैसे लागू किए जाते हैं) देखें: Itanium C ++ ABI: ऐरे ऑपरेटर नया कुकीज़


मैं केवल यही चाहता हूं कि हर कंपाइलर C ++ के लिए कुछ डॉक्यूमेंटेड ABI को देखे । लिंक के लिए +1, जिसे मैंने पहले देखा है। धन्यवाद।
डॉन वेकफील्ड

0

आप उपयोग कर सकते हैं नहीं हटाना एक सरणी के लिए, और आप उपयोग नहीं कर सकते नष्ट [] एक गैर सरणी के लिए।


8
मुझे लगता है कि आपको मतलब नहीं होना चाहिए , क्योंकि आपका औसत संकलक दुरुपयोग का पता लगाने वाला नहीं है।
डॉन वेकफील्ड

0

"अपरिभाषित व्यवहार" का सीधा सा मतलब है कि भाषा का कोई भी अनुमान नहीं है कि क्या होगा। इसका मतलब यह नहीं है कि कुछ बुरा होगा।

तो इन कामों को याद रखना किसका काम है? क्या OS पृष्ठभूमि में कुछ प्रकार का रिकॉर्ड रखता है? (मेरा मतलब है, मुझे एहसास है कि मैंने यह कहकर इस पद की शुरुआत की कि क्या होता है अपरिभाषित है, लेकिन तथ्य यह है कि, 'हत्या की होड़' परिदृश्य ऐसा नहीं होता है, इसलिए इसलिए व्यावहारिक दुनिया में कोई याद कर रहा है।)

आमतौर पर यहां दो लेयर हैं। अंतर्निहित मेमोरी मैनेजर और C ++ कार्यान्वयन।

सामान्य तौर पर मेमोरी मैनेजर याद रखेगा (अन्य बातों के अलावा) मेमोरी के ब्लॉक का आकार जो आवंटित किया गया था। यह उस ब्लॉक से बड़ा हो सकता है जो C ++ कार्यान्वयन के लिए कहा गया है। आमतौर पर मेमोरी मैनेजर मेमोरी के आवंटित ब्लॉक से पहले मेटाडेटा को स्टोर करेगा।

C ++ कार्यान्वयन आम तौर पर केवल सरणी के आकार को याद रखेगा यदि उसे अपने स्वयं के उद्देश्यों के लिए ऐसा करने की आवश्यकता होती है, आमतौर पर क्योंकि प्रकार में गैर-ट्राइबल विनाशकारी होता है।

इसलिए एक तुच्छ विध्वंसक प्रकार के लिए "हटाएं" और "हटाएं []" का कार्यान्वयन आम तौर पर समान है। C ++ कार्यान्वयन केवल सूचक को अंतर्निहित मेमोरी मैनेजर में पास करता है। कुछ इस तरह

free(p)

गैर-तुच्छ विध्वंसक "डिलीट" और "डिलीट []" प्रकार के लिए दूसरी ओर अलग होने की संभावना है। "डिलीट" कुछ की तरह होगा (जहां T वह प्रकार है जो सूचक इंगित करता है)

p->~T();
free(p);

जबकि "हटाएं []" कुछ इस तरह होगा।

size_t * pcount = ((size_t *)p)-1;
size_t count = *count;
for (size_t i=0;i<count;i++) {
  p[i].~T();
}
char * pmemblock = ((char *)p) - max(sizeof(size_t),alignof(T));
free(pmemblock);

-1

वस्तुओं की सरणी के माध्यम से पुनरावृत्ति करें और उनमें से प्रत्येक के लिए विध्वंसक कॉल करें। मैंने यह सरल कोड डायन ओवरलोड नया [] बनाया है और [] एक्सप्रेशंस को डिलीट किया है और मेमोरी को डिले करने के लिए टेम्प्लेट फ़ंक्शन प्रदान करता है और ज़रूरत पड़ने पर प्रत्येक ऑब्जेक्ट के लिए डिस्ट्रक्टर को कॉल करता है:

// overloaded new expression 
void* operator new[]( size_t size )
{
    // allocate 4 bytes more see comment below 
    int* ptr = (int*)malloc( size + 4 );

    // set value stored at address to 0 
    // and shift pointer by 4 bytes to avoid situation that
    // might arise where two memory blocks 
    // are adjacent and non-zero
    *ptr = 0;
    ++ptr; 

    return ptr;
}
//////////////////////////////////////////

// overloaded delete expression 
void static operator delete[]( void* ptr )
{
    // decrement value of pointer to get the
    // "Real Pointer Value"
    int* realPtr = (int*)ptr;
    --realPtr;

    free( realPtr );
}
//////////////////////////////////////////

// Template used to call destructor if needed 
// and call appropriate delete 
template<class T>
void Deallocate( T* ptr )
{
    int* instanceCount = (int*)ptr;
    --instanceCount;

    if(*instanceCount > 0) // if larger than 0 array is being deleted
    {
        // call destructor for each object
        for(int i = 0; i < *instanceCount; i++)
        {
            ptr[i].~T();
        }
        // call delete passing instance count witch points
        // to begin of array memory 
        ::operator delete[]( instanceCount );
    }
    else
    {
        // single instance deleted call destructor
        // and delete passing ptr
        ptr->~T();
        ::operator delete[]( ptr );
    }
}

// Replace calls to new and delete
#define MyNew ::new
#define MyDelete(ptr) Deallocate(ptr)

// structure with constructor/ destructor
struct StructureOne
{
    StructureOne():
    someInt(0)
    {}
    ~StructureOne() 
    {
        someInt = 0;
    }

    int someInt;
};
//////////////////////////////

// structure without constructor/ destructor
struct StructureTwo
{
    int someInt;
};
//////////////////////////////


void main(void)
{
    const unsigned int numElements = 30;

    StructureOne* structOne = nullptr;
    StructureTwo* structTwo = nullptr;
    int* basicType = nullptr;
    size_t ArraySize = 0;

/**********************************************************************/
    // basic type array 

    // place break point here and in new expression
    // check size and compare it with size passed 
    // in to new expression size will be the same
    ArraySize = sizeof( int ) * numElements;

    // this will be treated as size rather than object array as there is no 
    // constructor and destructor. value assigned to basicType pointer
    // will be the same as value of "++ptr" in new expression
    basicType = MyNew int[numElements];

    // Place break point in template function to see the behavior
    // destructors will not be called and it will be treated as 
    // single instance of size equal to "sizeof( int ) * numElements"
    MyDelete( basicType );

/**********************************************************************/
    // structure without constructor and destructor array 

    // behavior will be the same as with basic type 

    // place break point here and in new expression
    // check size and compare it with size passed 
    // in to new expression size will be the same
    ArraySize = sizeof( StructureTwo ) * numElements;

    // this will be treated as size rather than object array as there is no 
    // constructor and destructor value assigned to structTwo pointer
    // will be the same as value of "++ptr" in new expression
    structTwo = MyNew StructureTwo[numElements]; 

    // Place break point in template function to see the behavior
    // destructors will not be called and it will be treated as 
    // single instance of size equal to "sizeof( StructureTwo ) * numElements"
    MyDelete( structTwo );

/**********************************************************************/
    // structure with constructor and destructor array 

    // place break point check size and compare it with size passed in
    // new expression size in expression will be larger by 4 bytes
    ArraySize = sizeof( StructureOne ) * numElements;

    // value assigned to "structOne pointer" will be different 
    // of "++ptr" in new expression  "shifted by another 4 bytes"
    structOne = MyNew StructureOne[numElements];

    // Place break point in template function to see the behavior
    // destructors will be called for each array object 
    MyDelete( structOne );
}
///////////////////////////////////////////

-2

बस एक वर्ग के अंदर एक विध्वंसक को परिभाषित करें और अपने कोड को दोनों सिंटैक्स के साथ निष्पादित करें

delete pointer

delete [] pointer

उत्पादन के अनुसार यू समाधान पा सकते हैं


जब आप एक सरणी प्रकार नया हटाते हैं तो [] का उपयोग करें। उदाहरण के लिए int * a = new int; int * b = नया int [5]; हटा दें; हटाना [] बी;
रेखा केश मोहन

-3

उत्तर:

int * pArray = नया int [5];

int size = * (पृष्ठ -1);

ऊपर पोस्ट किया गया सही नहीं है और अमान्य मान उत्पन्न करता है। "-1" तत्वों को गिनता है 64 बिट विंडोज ओएस पर सही बफर आकार पंर - 4 बाइट्स पते में रहता है

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.