stringstream, string, and char * रूपांतरण भ्रम


141

मेरे सवाल को उबाला जा सकता है, स्ट्रिंग stringstream.str().c_str()स्मृति में लाइव कहां से लौटती है, और इसे क्यों नहीं सौंपा जा सकता है const char*?

यह कोड उदाहरण मुझे इससे बेहतर समझाएगा

#include <string>
#include <sstream>
#include <iostream>

using namespace std;

int main()
{
    stringstream ss("this is a string\n");

    string str(ss.str());

    const char* cstr1 = str.c_str();

    const char* cstr2 = ss.str().c_str();

    cout << cstr1   // Prints correctly
        << cstr2;   // ERROR, prints out garbage

    system("PAUSE");

    return 0;
}

अनुमान है कि एक बग के लिए stringstream.str().c_str()सौंपा जा सकता है const char*जो मुझे नीचे ट्रैक करने के लिए कुछ समय लगा।

बोनस बिंदुओं के लिए, क्या कोई समझा सकता है कि coutकथन को प्रतिस्थापित क्यों किया जाए

cout << cstr            // Prints correctly
    << ss.str().c_str() // Prints correctly
    << cstr2;           // Prints correctly (???)

तार को सही ढंग से प्रिंट करता है?

मैं विजुअल स्टूडियो 2008 में संकलन कर रहा हूं।

जवाबों:


201

stringstream.str()एक अस्थायी स्ट्रिंग ऑब्जेक्ट देता है जो पूर्ण अभिव्यक्ति के अंत में नष्ट हो जाता है। यदि आपको उस ( stringstream.str().c_str()) से एक सी स्ट्रिंग के लिए एक पॉइंटर मिलता है , तो यह एक स्ट्रिंग को इंगित करेगा जो हटा दिया जाता है जहां बयान समाप्त होता है। इसलिए आपका कोड कचरा प्रिंट करता है।

आप उस अस्थायी स्ट्रिंग ऑब्जेक्ट को किसी अन्य स्ट्रिंग ऑब्जेक्ट पर कॉपी कर सकते हैं और उस से C स्ट्रिंग ले सकते हैं:

const std::string tmp = stringstream.str();
const char* cstr = tmp.c_str();

ध्यान दें कि मैंने अस्थायी स्ट्रिंग की है const, क्योंकि इसमें कोई भी बदलाव इसके कारण पुन: आवंटन कर सकता है और इस प्रकार cstrअमान्य हो सकता है। कॉल के परिणाम को पूरी तरह से स्टोर न करना str()और cstrकेवल पूर्ण अभिव्यक्ति के अंत तक उपयोग करना सुरक्षित है :

use_c_str( stringstream.str().c_str() );

बेशक, उत्तरार्द्ध आसान नहीं हो सकता है और नकल करना बहुत महंगा हो सकता है। इसके बजाय आप क्या कर सकते हैं अस्थायी को एक constसंदर्भ में बांधना है । यह अपने जीवनकाल को संदर्भ के जीवनकाल तक विस्तारित करेगा:

{
  const std::string& tmp = stringstream.str();   
  const char* cstr = tmp.c_str();
}

IMO यह सबसे अच्छा उपाय है। दुर्भाग्य से यह बहुत अच्छी तरह से ज्ञात नहीं है।


13
यह ध्यान दिया जाना चाहिए कि एक प्रतिलिपि (जैसा कि आपके पहले उदाहरण में) आवश्यक रूप से किसी भी ओवरहेड का परिचय नहीं देगा - अगर str()इस तरह से लागू किया जाता है कि आरवीओ (जो बहुत संभावना है) में किक कर सकता है, संकलक को सीधे परिणाम के निर्माण की अनुमति है में tmp, अस्थायी eliding; और कोई भी आधुनिक C ++ कंपाइलर ऐसा करेगा जब अनुकूलन सक्षम हो। बेशक, बाइंड-टू-कॉन्स्ट-रेफरेंस सॉल्यूशन नो-कॉपी की गारंटी देता है, इसलिए यह बेहतर हो सकता है - लेकिन मुझे लगा कि यह अभी भी स्पष्ट करना है।
पावेल मिनाएव

1
"बेशक, बाइंड-टू-कॉन्स्ट-रेफरेंस सॉल्यूशन नो-कॉपी की गारंटी देता है" <- यह नहीं करता है। C ++ 03 में, कॉपी कंस्ट्रक्टर को एक्सेस करने की आवश्यकता है और कार्यान्वयन को इनिशलाइज़र को कॉपी करने और कॉपी के संदर्भ को बांधने की अनुमति है।
जोहान्स शाउब -

1
आपका पहला उदाहरण गलत है। C_str () द्वारा दिया गया मान क्षणिक है। वर्तमान बयान के अंत के बाद इस पर भरोसा नहीं किया जा सकता है। इस प्रकार आप किसी फ़ंक्शन को मान देने के लिए इसका उपयोग कर सकते हैं लेकिन आपको स्थानीय चर के लिए c_str () का परिणाम असाइन नहीं करना चाहिए।
मार्टिन यॉर्क

2
@ नोट: आप तकनीकी रूप से सही हैं। सूचक तब तक मान्य है जब तक कि स्ट्रिंग पर अगली गैर-लागत विधि कॉल न हो। समस्या यह है कि उपयोग स्वाभाविक रूप से खतरनाक है। हो सकता है कि मूल डेवलपर के लिए नहीं (हालांकि इस मामले में यह था) लेकिन विशेष रूप से बाद के रखरखाव को ठीक करने के लिए, इस तरह का कोड बेहद नाजुक हो जाता है। यदि आप ऐसा करना चाहते हैं, तो आपको पॉइंटर्स स्कोप को लपेटना चाहिए ताकि इसका उपयोग संभव हो सके (अभिव्यक्ति की लंबाई सबसे अच्छा)।
मार्टिन यॉर्क

1
@ एसबीआई: ठीक है, धन्यवाद, यह अधिक स्पष्ट है। सख्ती से बोलना, हालांकि, चूंकि 'स्ट्रिंग str' संस्करण उपरोक्त कोड में संशोधित नहीं है, इसलिए str.c_str () पूरी तरह से वैध है, लेकिन मैं अन्य मामलों में संभावित खतरे की सराहना करता हूं।
विलियम नाइट

13

आप जो कर रहे हैं वह एक अस्थायी पैदा कर रहा है। यह अस्थायी संकलक द्वारा निर्धारित दायरे में मौजूद है, जैसे कि यह काफी हद तक जहां वह जा रहा है की आवश्यकताओं को पूरा करने के लिए पर्याप्त है।

जैसे ही बयान const char* cstr2 = ss.str().c_str();पूरा हो जाता है, संकलक अस्थायी स्ट्रिंग को इधर-उधर रखने का कोई कारण नहीं देखता है, और यह नष्ट हो जाता है, और इस प्रकार आपकी const char *मेमोरी को फ्री होल्ड करने की ओर इशारा करता है।

आपके कथन का string str(ss.str());अर्थ है कि अस्थायी का उपयोग कंस्ट्रक्टर में उस stringचर के लिए किया जाता है, strजिसे आपने स्थानीय स्टैक पर रखा है, और जब तक आप अपेक्षा करते हैं, तब तक आसपास रहता है: ब्लॉक के अंत तक, या आपके द्वारा लिखे गए फ़ंक्शन तक। इसलिए const char *जब आप कोशिश करते हैं तब भी याददाश्त अच्छी रहती है cout


6

इस पंक्ति में:

const char* cstr2 = ss.str().c_str();

ss.str()स्ट्रिंगस्ट्रीम की सामग्री की एक प्रति बनाएगा। जब आप c_str()एक ही लाइन पर कॉल करते हैं, तो आप वैध डेटा को संदर्भित करेंगे, लेकिन उस लाइन के बाद स्ट्रिंग नष्ट हो जाएगी, जिससे आपकी char*जाने-माने मेमोरी को इंगित किया जा सकेगा ।


5

Std :: seststr () द्वारा लौटाए गए स्ट्रिंग ऑब्जेक्ट एक अस्थायी ऑब्जेक्ट है जो एक लाइफ टाइम अभिव्यक्ति तक सीमित होगा। इसलिए आप कचरा न मिलने पर एक अस्थायी वस्तु को पॉइंटर सौंप सकते हैं।

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

#include <string>
#include <sstream>
#include <iostream>

using namespace std;

int main()
{
    stringstream ss("this is a string\n");

    string str(ss.str());

    const char* cstr1 = str.c_str();

    const std::string& resultstr = ss.str();
    const char* cstr2 = resultstr.c_str();

    cout << cstr1       // Prints correctly
        << cstr2;       // No more error : cstr2 points to resultstr memory that is still alive as we used the const reference to keep it for a time.

    system("PAUSE");

    return 0;
}

इस तरह आप लंबे समय तक स्ट्रिंग प्राप्त करते हैं।

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

 std::string resultstr = ss.str();
 const char* cstr2 = resultstr.c_str();

बेहतर और सरल होगा।


5

ss.str()अस्थायी आरंभीकरण के बाद नष्ट कर दिया है की cstr2पूरा हो गया है। इसलिए जब आप इसे प्रिंट करते हैं cout, तो उस std::stringअस्थायी के साथ जुड़े सी-स्ट्रिंग को लंबे समय से नष्ट कर दिया गया है, और इस तरह आप भाग्यशाली होंगे यदि यह दुर्घटनाग्रस्त हो जाता है और यह भाग्यशाली है कि यह कचरा प्रिंट करता है या काम नहीं करता है।

const char* cstr2 = ss.str().c_str();

C- स्ट्रिंग जहां cstr1इंगित करता है, हालांकि, एक स्ट्रिंग के साथ जुड़ा हुआ है जो अभी भी आपके पास मौजूद है cout- तो यह सही ढंग से परिणाम प्रिंट करता है।

निम्नलिखित कोड में, पहला cstrसही है (मुझे लगता है कि यह cstr1वास्तविक कोड में है?)। दूसरा सी-स्ट्रिंग को अस्थायी स्ट्रिंग ऑब्जेक्ट से संबंधित प्रिंट करता है ss.str()। ऑब्जेक्ट उस पूर्ण-अभिव्यक्ति का मूल्यांकन करने के अंत में नष्ट हो जाता है जिसमें यह प्रकट होता है। पूर्ण-अभिव्यक्ति संपूर्ण cout << ...अभिव्यक्ति है - इसलिए जब सी-स्ट्रिंग आउटपुट होता है, तो संबंधित स्ट्रिंग ऑब्जेक्ट अभी भी मौजूद है। के लिए cstr2- यह शुद्ध बुरा है कि यह सफल होता है। यह संभवतः आंतरिक रूप से नए अस्थायी के लिए उसी संग्रहण स्थान को चुनता है जिसे उसने पहले से ही उपयोग करने के लिए अस्थायी के लिए चुना था cstr2। इससे दुर्घटना हो सकती है।

cout << cstr            // Prints correctly
    << ss.str().c_str() // Prints correctly
    << cstr2;           // Prints correctly (???)

c_str()वसीयत की वापसी आमतौर पर आंतरिक स्ट्रिंग बफर को इंगित करती है - लेकिन यह एक आवश्यकता नहीं है। यदि इसका आंतरिक कार्यान्वयन उदाहरण के लिए सन्निहित नहीं है, तो स्ट्रिंग एक बफर बना सकती है (यह अच्छी तरह से संभव है - लेकिन अगले सी ++ मानक में, तार को संचित रूप से संग्रहीत करने की आवश्यकता है)।

जीसीसी में, स्ट्रिंग्स संदर्भ गिनती और कॉपी-ऑन-राइट का उपयोग करते हैं। इस प्रकार, आप पाएंगे कि निम्नलिखित सही है (यह मेरे GCC संस्करण पर कम से कम)

string a = "hello";
string b(a);
assert(a.c_str() == b.c_str());

दो तार यहाँ एक ही बफर साझा करते हैं। जब आप उनमें से किसी एक को बदलते हैं, तो बफर की प्रतिलिपि बनाई जाएगी और प्रत्येक अपनी अलग कॉपी रखेगा। अन्य स्ट्रिंग कार्यान्वयन अलग चीजें करते हैं, हालांकि।

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