stringstream, string ve char * dönüşüm karışıklığı


141

Benim sorum şu şekilde kaynatılabilir, dize stringstream.str().c_str()bellekte canlı olarak geri döndü ve neden a'ya atanamıyor const char*?

Bu kod örneği benden daha iyi açıklayacaktır

#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;
}

Bir yola stringstream.str().c_str()atanabilecek varsayım, const char*izlememi biraz zaman alan bir hataya yol açtı.

Bonus puanları için, herkes coutifadeyi neden değiştirdiğini açıklayabilir mi?

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

dizeleri doğru yazdırır?

Visual Studio 2008'de derliyorum.

Yanıtlar:


201

stringstream.str()tam ifadenin sonunda yok edilen geçici bir dize nesnesi döndürür. Bu ( stringstream.str().c_str()) öğesinden bir C dizesine işaretçi alırsanız , ifadenin sona erdiği yerde silinen bir dizeye işaret eder. Bu yüzden kodunuz çöp yazdırıyor.

Bu geçici dize nesnesini başka bir dize nesnesine kopyalayabilir ve C dizesini bu nesneden alabilirsiniz:

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

Geçici dize yaptığımı unutmayın const, çünkü herhangi bir değişiklik yeniden tahsis ve böylece cstrgeçersiz hale neden olabilir . Bu nedenle, çağrının sonucunu hiç saklamamak str()ve cstryalnızca tam ifadenin sonuna kadar kullanmamak daha güvenlidir :

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

Tabii ki, ikincisi kolay olmayabilir ve kopyalama çok pahalı olabilir. Bunun yerine geçici olanı bir constreferansa bağlamaktır . Bu, kullanım ömrünü referansın kullanım ömrünü uzatacaktır:

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

IMO en iyi çözümdür. Ne yazık ki çok iyi bilinmemektedir.


13
Bir kopya yapmanın (ilk örneğinizde olduğu gibi) mutlaka herhangi bir ek yük str()getirmeyeceği unutulmamalıdır - eğer RVO devreye girecek şekilde (çok muhtemeldir) uygulanırsa, derleyicinin sonucu doğrudan oluşturmasına izin verilir içine tmpgeçici eliding; ve tüm modern C ++ derleyicileri optimizasyonlar etkinleştirildiğinde bunu yapar. Elbette, bağlama-bağlama-referans çözümü kopyalanmayı garanti etmez, bu yüzden tercih edilebilir - ama yine de netleştirmeye değer olduğunu düşündüm.
Pavel Minaev

1
"Elbette, bind-to-const-referans çözümü kopyasız garanti" <- değil. C ++ 03'te, kopya oluşturucunun erişilebilir olması gerekir ve uygulamanın başlatıcıyı kopyalamasına ve referansı kopyaya bağlamasına izin verilir.
Johannes Schaub - litb

1
İlk örneğiniz yanlış. C_str () tarafından döndürülen değer geçicidir. Mevcut ifadenin bitiminden sonra güvenilemez. Böylece bir fonksiyona bir değer iletmek için kullanabilirsiniz ancak ASLA c_str () sonucunu yerel bir değişkene atamamalısınız.
Martin York

2
@litb: Teknik olarak haklısın. İşaretçi, dizede bir sonraki maliyetsiz yöntem çağrısına kadar geçerlidir. Sorun, kullanımın doğası gereği tehlikeli olmasıdır. Belki orijinal geliştiriciye değil (bu durumda olsa da), ancak daha sonraki bakım düzeltmelerine göre, bu tür kod son derece kırılgan hale gelir. Bunu yapmak istiyorsanız, işaretçilerin kapsamını, kullanımının olabildiğince kısa olması (en iyisi ifadenin uzunluğu) olacak şekilde sarmanız gerekir.
Martin York

1
@sbi: Tamam, teşekkürler, bu daha açık. Kesin olarak, 'string str' var yukarıdaki kodda değiştirilmediğinden, str.c_str () tamamen geçerli kalır, ancak diğer durumlarda potansiyel tehlikeyi takdir ediyorum.
William Knight

13

Yaptığınız şey geçici bir şey yaratmak. Bu geçici, derleyici tarafından belirlenen bir kapsamda mevcuttur, böylece nereye gittiğinin gereksinimlerini karşılayacak kadar uzun olur.

Deyim const char* cstr2 = ss.str().c_str();tamamlanır tamamlanmaz, derleyici geçici dizgiyi tutmak için bir neden görmez ve bu yok edilir ve bu nedenle const char *belleğinizin serbest bırakıldığını gösterir.

İfadeniz string str(ss.str());, geçici öğenin yapıcıda yerel yığına koyduğunuz stringdeğişken için kullanıldığını strve beklediğiniz sürece kaldığını gösterir: bloğun sonuna veya yazdığınız işlevin sonuna kadar. Bu nedenle const char *içinde denemek hala içinde iyi bir bellek cout.


6

Bu satırda:

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

ss.str()stringstream içeriğinin bir kopyasını oluşturur. Aradığınızda c_str()aynı satırda, meşru verilere başvurulması olacak, ama bu satırdan sonra dizedir bırakarak imha edilecek char*sahipsiz belleğe işaret edecek.


5

Ss.str () tarafından döndürülen std :: string nesnesi, yaşam süresi ifadeyle sınırlı olan geçici bir nesnedir. Dolayısıyla, çöp almadan geçici bir nesneye işaretçi atayamazsınız.

Şimdi, bir istisna vardır: geçici nesneyi elde etmek için bir const referansı kullanırsanız, onu daha geniş bir yaşam süresi için kullanmak yasaldır. Örneğin şunları yapmalısınız:

#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;
}

Bu şekilde ipi daha uzun süre alırsınız.

Şimdi, RVO adı verilen, derleyici bir işlev çağrısı yoluyla bir başlatma görürse ve bu işlev geçici olarak döndürürse, kopyayı yapmayacağını, ancak atanan değerin geçici olmasını sağlayan bir tür optimizasyon olduğunu bilmelisiniz. . Bu şekilde gerçekten bir referans kullanmanıza gerek kalmaz, sadece bunun gerekli olduğunu kopyalamayacağından emin olmak istiyorsanız. Böylece:

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

daha iyi ve daha basit olurdu.


5

ss.str()Geçici başlatma sonra imha edilir cstr2tamamlanır. Bu nedenle, yazdırdığınızda cout, bu std::stringgeçici ile ilişkili c-string uzun zamandır istenmemektedir ve bu nedenle çöküp savunduğunda şanslı olacaksınız ve çöp yazdırıyorsa veya işe yarıyorsa şanslı olmayacaksınız.

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

cstr1Bununla birlikte, işaret eden C-dizesi , bunu yaptığınız sırada hala var olan bir dize ile ilişkilidir cout- böylece sonucu doğru olarak yazdırır.

Aşağıdaki kodda, ilk cstrdoğrudur ( cstr1gerçek kodda olduğunu varsayalım ?). İkincisi geçici dize nesnesiyle ilişkili c-dizgisini yazdırır ss.str(). Nesne, içinde göründüğü tam ifadeyi değerlendirdikten sonra yok edilir. Tam ifade tüm cout << ...ifadedir - bu nedenle c-string çıktı alınırken ilişkili dize nesnesi hala vardır. Çünkü cstr2- başardığı saf bir kötülük. Büyük olasılıkla dahili olarak, başlangıç ​​için kullanılan geçici için zaten seçtiği yeni geçici için aynı depolama yerini seçer cstr2. Ayrıca çökebilir.

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

Dönüşü c_str()genellikle yalnızca iç dize arabelleğine işaret eder - ancak bu bir zorunluluk değildir. Dize, dahili uygulaması örneğin bitişik değilse bir arabellek oluşturabilir (bu mümkün - ancak bir sonraki C ++ Standardında, dizelerin bitişik olarak depolanması gerekir).

GCC'de, dizeler referans sayma ve yazma üzerine kopyalama kullanır. Böylece, aşağıdakilerin doğru olduğunu göreceksiniz (en azından GCC sürümümde)

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

İki dize burada aynı arabelleği paylaşır. Bunlardan birini değiştirdiğinizde, arabellek kopyalanacak ve her biri ayrı bir kopyasını tutacaktır. Diğer dize uygulamaları farklı şeyler yapar.

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.