confusione di stringhe, stringhe e char *


141

La mia domanda può essere ridotta a, dove viene restituita la stringa dal stringstream.str().c_str()vivo in memoria e perché non può essere assegnata a un const char*?

Questo esempio di codice lo spiegherà meglio di me

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

Il presupposto che stringstream.str().c_str()potrebbe essere assegnato a un const char*bug ha portato a un bug che mi ha impiegato un po 'di tempo per rintracciare.

Per i punti bonus, qualcuno può spiegare perché sostituire la coutdichiarazione con

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

stampa correttamente le stringhe?

Sto compilando in Visual Studio 2008.

Risposte:


201

stringstream.str()restituisce un oggetto stringa temporaneo che viene distrutto alla fine dell'espressione completa. Se ottieni un puntatore a una stringa C da that ( stringstream.str().c_str()), punterà a una stringa che viene eliminata dove termina l'istruzione. Ecco perché il tuo codice stampa la spazzatura.

È possibile copiare quell'oggetto stringa temporaneo su qualche altro oggetto stringa e prendere la stringa C da quella:

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

Si noti che ho creato la stringa temporanea const, poiché eventuali modifiche potrebbero causarne la ridistribuzione e pertanto rendere cstrnon valido. È quindi più sicuro non memorizzare affatto il risultato della chiamata str()e utilizzarlo cstrsolo fino alla fine dell'espressione completa:

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

Naturalmente, quest'ultimo potrebbe non essere facile e la copia potrebbe essere troppo costosa. Quello che puoi fare invece è legare il temporaneo a un constriferimento. Ciò prolungherà la sua durata alla durata del riferimento:

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

IMO è la soluzione migliore. Purtroppo non è molto conosciuto.


13
Va notato che fare una copia (come nel tuo primo esempio) non introdurrà necessariamente alcun sovraccarico - se str()è implementato in modo tale che RVO possa dare il via (il che è molto probabile), al compilatore è permesso costruire direttamente il risultato in tmp, eludere il temporaneo; e qualsiasi compilatore C ++ moderno lo farà quando saranno abilitate le ottimizzazioni. Naturalmente, la soluzione bind-to-const-reference garantisce l'assenza di copia, quindi potrebbe essere preferibile, ma ho pensato che valesse la pena chiarire.
Pavel Minaev,

1
"Naturalmente, la soluzione bind-to-const-reference garantisce la non copia" <- non lo fa. In C ++ 03, il costruttore della copia deve essere accessibile e l'implementazione può copiare l'inizializzatore e associare il riferimento alla copia.
Johannes Schaub - litb,

1
Il tuo primo esempio è sbagliato. Il valore restituito da c_str () è transitorio. Non è possibile fare affidamento dopo la fine della dichiarazione corrente. Quindi puoi usarlo per passare un valore a una funzione, ma non devi MAI assegnare il risultato di c_str () a una variabile locale.
Martin York,

2
@litb: tecnicamente hai ragione. Il puntatore è valido fino alla successiva chiamata del metodo senza costi sulla stringa. Il problema è che l'uso è intrinsecamente pericoloso. Forse non per lo sviluppatore originale (anche se in questo caso lo era) ma soprattutto per le successive correzioni di manutenzione, questo tipo di codice diventa estremamente fragile. Se vuoi farlo, dovresti avvolgere l'ambito dei puntatori in modo che il suo utilizzo sia il più breve possibile (la migliore è la lunghezza dell'espressione).
Martin York,

1
@sbi: Ok, grazie, è più chiaro. A rigor di termini, tuttavia, poiché la 'stringa str' var non è modificata nel codice sopra, str.c_str () rimane perfettamente valido, ma apprezzo il potenziale pericolo in altri casi.
William Knight,

13

Quello che stai facendo è creare un temporaneo. Tale temporaneo esiste in un ambito determinato dal compilatore, in modo tale che sia abbastanza lungo da soddisfare i requisiti di dove sta andando.

Non appena l'istruzione const char* cstr2 = ss.str().c_str();è completa, il compilatore non vede alcun motivo per mantenere la stringa temporanea in giro, ed è distrutta, e quindi const char *stai indicando memoria libera.

La tua affermazione string str(ss.str());indica che il temporaneo viene utilizzato nel costruttore per la stringvariabile strche hai inserito nello stack locale e che rimane attivo per tutto il tempo che ti aspetteresti: fino alla fine del blocco o funzione che hai scritto. Pertanto, l' const char *interno è ancora una buona memoria quando si tenta di cout.


6

In questa linea:

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

ss.str()farà una copia del contenuto del flusso di stringhe. Quando chiami c_str()sulla stessa linea, ti riferirai a dati legittimi, ma dopo quella linea la stringa verrà distrutta, lasciandoti char*puntare alla memoria sconosciuta.


5

L'oggetto std :: string restituito da ss.str () è un oggetto temporaneo che avrà una durata limitata all'espressione. Quindi non è possibile assegnare un puntatore a un oggetto temporaneo senza ottenere il cestino.

Ora, c'è un'eccezione: se usi un riferimento const per ottenere l'oggetto temporaneo, è legale usarlo per una vita più ampia. Ad esempio dovresti fare:

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

In questo modo ottieni la stringa più a lungo.

Ora, devi sapere che esiste un tipo di ottimizzazione chiamata RVO che afferma che se il compilatore vede un'inizializzazione tramite una chiamata di funzione e tale funzione restituisce un temporaneo, non eseguirà la copia ma renderà il valore assegnato solo temporaneo . In questo modo non è necessario utilizzare effettivamente un riferimento, è solo se si desidera essere sicuri che non copierà che è necessario. Quindi facendo:

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

sarebbe meglio e più semplice.


5

Il ss.str()temporaneo viene distrutto al termine dell'inizializzazione di cstr2. Quindi, quando lo stampi cout, la stringa di c che era associata a quel std::stringtemporaneo è stata a lungo destabilizzata, e quindi sarai fortunato se si blocca e afferma, e non fortunato se stampa immondizia o sembra funzionare.

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

La stringa C in cui cstr1punta, tuttavia, è associata a una stringa che esiste ancora nel momento in cui si esegue l'operazione cout, quindi stampa correttamente il risultato.

Nel seguente codice, il primo cstrè corretto (suppongo sia cstr1nel codice reale?). Il secondo stampa la stringa c associata all'oggetto stringa temporaneo ss.str(). L'oggetto viene distrutto al termine della valutazione dell'espressione completa in cui appare. L'espressione completa è l'intera cout << ...espressione, quindi mentre la stringa C viene emessa, l'oggetto stringa associato esiste ancora. Perché cstr2- è pura cattiveria che ci riesca. Molto probabilmente sceglie internamente la stessa posizione di archiviazione per il nuovo temporaneo che ha già scelto per il temporaneo utilizzato per inizializzare cstr2. Potrebbe anche schiantarsi.

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

Il ritorno di c_str()solito punta semplicemente al buffer di stringhe interno, ma questo non è un requisito. La stringa potrebbe costituire un buffer se la sua implementazione interna non è contigua per esempio (questo è possibile - ma nel prossimo standard C ++, le stringhe devono essere memorizzate in modo contiguo).

In GCC, le stringhe usano il conteggio dei riferimenti e la copia su scrittura. Quindi, scoprirai che quanto segue vale (lo fa, almeno sulla mia versione di GCC)

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

Le due stringhe condividono lo stesso buffer qui. Al momento della modifica di uno di essi, il buffer verrà copiato e ciascuno manterrà la sua copia separata. Tuttavia, altre implementazioni di stringhe fanno cose diverse.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.