2016-03-14 18 views
8

Ho il seguente codice:Perché chiamare std :: string.c_str() su una funzione che restituisce una stringa non funziona?

std::string getString() { 
    std::string str("hello"); 
    return str; 
} 

int main() { 
    const char* cStr = getString().c_str(); 
    std::cout << cStr << std::endl; // this prints garbage 
} 

Quello che ho pensato che sarebbe accaduto è che getString() sarebbe restituire una copia di str (getString() restituisce per valore); pertanto, la copia di str rimarrebbe "in vita" nel numero main() fino a main() resi. Ciò renderebbe cStr punto in una posizione di memoria valida: il sottostante char[] o char* (o qualsiasi altra cosa) della copia di str restituito da getString() che rimane in main().

Tuttavia, questo ovviamente non è il caso, in quanto il programma emette spazzatura. Quindi, la domanda è, quando è str distrutta, e perché?

+0

Il tuo codice funziona bene per me. –

+13

@PriyanshGoel Il comportamento indefinito è così a volte. –

+0

Non riesco a capire perché il comportamento non è definito. –

risposta

18

getString() restituire una copia di str (getString() restituisce per valore);

È giusto.

in tal modo, la copia del str sarebbe rimasto "vivo" in main() fino main() rendimenti.

No, l'esemplare di rinvio è una temporanea std::string, che sarà distrutta alla fine della dichiarazione in cui è stato creato, vale a dire prima della std::cout << cStr << std::endl;. Quindi cStr diventa ciondolato, il dereferimento su di esso porta a UB, tutto è possibile.

È possibile copiare il valore temporaneo restituito su una variabile denominata o associarlo a un riferimento di valore o valore di riferimento di const (la durata del temporaneo verrà estesa fino a quando il riferimento non si estende). Come ad esempio:

std::string s1 = getString(); // s1 will be copy initialized from the temporary 
const char* cStr1 = s1.c_str(); 
std::cout << cStr1 << std::endl; // safe 

const std::string& s2 = getString(); // lifetime of temporary will be extended when bound to a const lvalue-reference 
const char* cStr2 = s2.c_str(); 
std::cout << cStr2 << std::endl; // safe 

std::string&& s3 = getString(); // similar with above 
const char* cStr3 = s3.c_str(); 
std::cout << cStr3 << std::endl; // safe 

Ecco una spiegazione da 10.4.10 oggetti temporanei [class.temp]] [The.C++ Programming.Language.Special.Edition.]:

A meno legato a un riferimento o utilizzato per inizializzare un oggetto denominato, un oggetto temporaneo viene distrutto alla fine dell'espressione completa in che è stato creato. Un'espressione completa è un'espressione che è non una sottoespressione di qualche altra espressione.

La classe di stringhe standard ha una funzione membro c_str() che restituisce una matrice di caratteri stile C, a terminazione zero (§3.5.1, §20.4.1). Inoltre, l'operatore + è definito per indicare la concatenazione di stringhe. Queste sono strutture molto utili per le stringhe. Tuttavia, in combinazione possono causare problemi oscuri. Per esempio:

void f(string& s1, string& s2, string& s3) 
{ 

    const char* cs = (s1 + s2).c_str(); 
    cout << cs ; 
    if (strlen(cs=(s2+s3).c_str())<8 && cs[0]==´a´) { 
     // cs used here 
    } 

} 

Probabilmente, la tua prima reazione è "ma non farlo", e sono d'accordo. Tuttavia, tale codice viene scritto, quindi vale la pena sapere come è interpretato .

Un oggetto temporaneo di stringa di classe viene creato per contenere s1 + s2. Successivamente, un puntatore a una stringa in stile C viene estratto da quell'oggetto. Quindi - alla fine dell'espressione - l'oggetto temporaneo viene cancellato. Ora, dove è stata allocata la stringa in stile C? Probabilmente come parte dell'oggetto temporaneo che contiene s1 + s2 e che non è garantito che l'archiviazione esista dopo che il temporaneo è stato distrutto. Di conseguenza, cs punti per archiviare deallocated. L'operazione di uscita cout < < cs potrebbe funzionare come previsto, ma che sarebbe pura fortuna. Un compilatore può rilevare e mette in guardia contro molte varianti di questo problema.

+0

Ma salvando lo std temporaneo :: string in una variabile come: std :: string str = getString(); non distruggerà la copia? perchè no? è perché il = usa il costruttore di copie? –

+0

Mi dispiace ma non riesco a capire questo. Sono d'accordo che str sarà distrutto ma poi ho già memorizzato il valore di string.c_str in un'altra variabile. Dovrebbe stampare ciao. –

+0

@FakeJake Sì, la variabile denominata 'str' sarà copia inizializzata dalla variabile temporanea, (anche se la copia verrà omessa di solito da RVO), quindi' str' sarà valido fino alla fine 'main()', quindi Sarò sicuro di usare 'str.c_str()' in 'main()'. – songyuanyao

4

Il problema è che si sta restituendo una variabile temporanea e su quella variabile temporanea che si sta facendo c_str function.

"c_str() restituisce un puntatore a una matrice che contiene una terminazione Null sequenza di caratteri (ad esempio, un C-stringa) rappresenta il valore corrente dell'oggetto stringa ( [http://www.cplusplus.com/reference/string/string/c_str/][1]).

In questo caso il puntatore sta puntando alla posizione di memoria che ora non è presente.

std::string getString() { 
     std::string str("hello"); 
     return str; // Will create Temporary object as it's return by value} 

    int main() { 
     const char* cStr = getString().c_str(); // Temporary object is destroyed 
     std::cout << cStr << std::endl; // this prints garbage } 

Solution è quello di copiare il vostro ob temporanea gettare correttamente nella posizione di memoria (creando una copia locale) e quindi usare c_str su quell'oggetto.

1

Come menzionato da altri, si sta utilizzando un puntatore temporaneo dopo che è già stato eliminato: questo è un classico esempio di heap dopo l'uso gratuito.

Ciò che posso aggiungere alle risposte degli altri è che è possibile rilevare facilmente tale utilizzo con disinfettanti di indirizzo gcc's o clang's.

Esempio:

#include <string> 
#include <iostream> 

std::string get() 
{ 
    return "hello"; 
} 

int main() 
{ 
    const char* c = get().c_str(); 
    std::cout << c << std::endl; 
} 

disinfettante uscita:

================================================================= 
==2951==ERROR: AddressSanitizer: heap-use-after-free on address 0x60300000eff8 at pc 0x7f78e27869bb bp 0x7fffc483e670 sp 0x7fffc483de20 
READ of size 6 at 0x60300000eff8 thread T0 
    #0 0x7f78e27869ba in strlen (/usr/lib64/libasan.so.2+0x6d9ba) 
    #1 0x39b4892ba0 in std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*) (/usr/lib64/libstdc++.so.6+0x39b4892ba0) 
    #2 0x400dd8 in main /tmp/tmep_string/main.cpp:12 
    #3 0x39aa41ed5c in __libc_start_main (/lib64/libc.so.6+0x39aa41ed5c) 
    #4 0x400c48 (/tmp/tmep_string/a.out+0x400c48) 

0x60300000eff8 is located 24 bytes inside of 30-byte region [0x60300000efe0,0x60300000effe) 
freed by thread T0 here: 
    #0 0x7f78e27ae6ea in operator delete(void*) (/usr/lib64/libasan.so.2+0x956ea) 
    #1 0x39b489d4c8 in std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() (/usr/lib64/libstdc++.so.6+0x39b489d4c8) 
    #2 0x39aa41ed5c in __libc_start_main (/lib64/libc.so.6+0x39aa41ed5c) 

previously allocated by thread T0 here: 
    #0 0x7f78e27ae1aa in operator new(unsigned long) (/usr/lib64/libasan.so.2+0x951aa) 
    #1 0x39b489c3c8 in std::string::_Rep::_S_create(unsigned long, unsigned long, std::allocator<char> const&) (/usr/lib64/libstdc++.so.6+0x39b489c3c8) 
    #2 0x400c1f (/tmp/tmep_string/a.out+0x400c1f) 

SUMMARY: AddressSanitizer: heap-use-after-free ??:0 strlen 
Shadow bytes around the buggy address: 
    0x0c067fff9da0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 
    0x0c067fff9db0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 
    0x0c067fff9dc0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 
    0x0c067fff9dd0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 
    0x0c067fff9de0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 
=>0x0c067fff9df0: fa fa fa fa fa fa fa fa fa fa fa fa fd fd fd[fd] 
    0x0c067fff9e00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 
    0x0c067fff9e10: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 
    0x0c067fff9e20: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 
    0x0c067fff9e30: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 
    0x0c067fff9e40: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 
Shadow byte legend (one shadow byte represents 8 application bytes): 
    Addressable:   00 
    Partially addressable: 01 02 03 04 05 06 07 
    Heap left redzone:  fa 
    Heap right redzone:  fb 
    Freed heap region:  fd 
    Stack left redzone:  f1 
    Stack mid redzone:  f2 
    Stack right redzone:  f3 
    Stack partial redzone: f4 
    Stack after return:  f5 
    Stack use after scope: f8 
    Global redzone:   f9 
    Global init order:  f6 
    Poisoned by user:  f7 
    Container overflow:  fc 
    Array cookie:   ac 
    Intra object redzone: bb 
    ASan internal:   fe 
==2951==ABORTING