2016-03-14 11 views
8

Aşağıdaki kod var:Neden bir dizeyi döndüren bir işlevde std :: string.c_str() çağrısı çalışmıyor?

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 
} 

Ne olacağını düşündüm getString() bir kopyasını str (değeriyle getString() getiri) ait iade edeceğini olduğu; Bu nedenle, str kopyası, main() içinde main() döndürene kadar "canlı" kalır. Bu, cStr noktasını geçerli bir bellek konumuna getirir: veya char* (ya da her neyse) tarafından 1 tarafından döndürülen içinde kalır. Bununla birlikte, program çıktı olarak çöp çıktığında, durum böyle değil. Yani, soru, ne zaman str imha edildi ve neden?

+0

Kodunuz benim için çalışıyor. –

+13

@PriyanshGoel Tanımsız davranış bazen böyle. –

+0

Tanımlanamayan davranış neden elde edemiyorum. –

cevap

18

getString()str (değeriyle getString() getiri) 'in bir kopyasını geri gönderecekti

Doğru.

böylece str kopyası main()main() kadar dönüşlerinde "canlı" kalmak istiyorum.

Hayır, döndürülen kopyası std::cout << cStr << std::endl; önce oluşturulduğu açıklamada, yani sonunda imha edilecek geçici std::string vardır. Sonra cStr sallanır, üzerinde dereference UB yol açar, her şey mümkündür. İade edilen geçici dosyayı adlandırılmış bir değişkene kopyalayabilir veya bir const lvalue referansına veya rvalue referansına bağlayabilirsiniz (referans süresi kapsam dışına çıkana kadar geçici sürenin uzunluğu uzatılacaktır). Burada

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 

bir açıklama 10.4.10 Geçici Nesne [class.temp]]: Mesela [The.C++ Programming.Language.Special.Edition.]:

sürece bağlı bir referansa veya adlandırılmış bir nesneyi başlatmak için kullanılan bir geçici nesnesi, oluşturulduğu içinde tam ifadenin sonunda yok edilir. Tam bir ifade, başka bir ifadenin bir alt ifadesi olmayan ifadesidir.

Standart dize sınıfı, 'un bir C stili, sıfır sonlu karakter dizisi (§3.5.1, §20.4.1) döndürdüğü bir c_str() işlevine sahiptir. Ayrıca, operatör + dizgi birleştirme anlamına gelir. Bunlar, dizeler için çok kullanışlı tesislerdir. Bununla birlikte, kombinasyon halinde belirsiz problemlere neden olabilirler. Örneğin :

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 
    } 

} 

Muhtemelen, ilk tepkidir "ama, böyle yapma" ve kabul ediyorum. Ancak, böyle bir kod yazılır, bu yüzden nasıl yorumlanır yorumlanır.

S1 + s2'yi tutmak için geçici bir sınıf dizesi nesnesi oluşturulur. Ardından, bu nesneden bir C stili dizeye işaretçi çıkarılır. Sonra - ifadenin sonunda - geçici nesne silinir. Şimdi, , C stili dizgisi nerede ayrıldı? Muhtemelen, geçici nesnenin s1 + s2 tutucusunun bir parçası olarak ve bu geçici olarak yok olduktan sonra o depolama garanti edilemez. Sonuç olarak, cs depolamak için noktalarını işaret eder. Çıkış işlemi < < cs beklendiği gibi çalışabilir, ancak bu şanssızlık olurdu. Bir derleyici algılar ve bu sorunun birçok varyantına karşı uyarır. Burada

+0

Olası geçici kaydetme :: string gibi bir değişkende: std :: string str = getString(); kopyayı yok etmeyecek misin? neden olmasın? çünkü = kopya kurucuyu kullanır? –

+0

Üzgünüm ama bunu anlayamıyorum. Str'nin yok olacağını kabul ediyorum ama daha sonra string.c_str değerini başka bir değişkenin içinde sakladım. Merhaba yazmalı. –

+0

@FakeJake Evet, adlandırılmış değişken 'str' (kopya RVT tarafından genellikle ihmal edilecek olsa bile) geçici değişken, daha sonra' str' geçerli olacak itibaren başlatılmış kopya edilecek kadar 'ana()' uçları, bunun yüzden 'main() 'içinde str.c_str() işlevini kullanmak güvenli olacaktır. – songyuanyao

4

Sorun geçici bir değişken iade ettiğiniz ve üzerinde geçici değişken, c_str işlevini yapıyor olmasıdır.

"c_str() işlev String nesnesi ( [http://www.cplusplus.com/reference/string/string/c_str/][1]) mevcut değerini temsil eden karakterleri bir boş sonlandırılmış dizisini (yani bir Cı-string) içeren bir dizi için bir işaretçi döndürür.

Bu durumda işaretçinizin artık mevcut olmadığı bellek konumuna işaret ediyor.

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 } 

Çözüm geçici ob kopyalamaktır Hafızaya doğru konumlandırın (yerel kopya oluşturarak) ve daha sonra c_str'yi bu nesne üzerinde kullanın.

1

Diğerleri tarafından belirtildiği gibi, daha önce silinmiş olduktan sonra geçici bir işaretçi kullanıyorsunuz - bu, ücretsiz kullanımından sonra yığınının klasik bir örneğidir.

Başkalarının cevaplarına neler ekleyebileceğim, gcc's veya clang's adres dezenfektanlarıyla bu tür kullanımları kolayca tespit edebilmenizdir.

Örnek:

#include <string> 
#include <iostream> 

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

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

çıkışı dezenfektanı:

================================================================= 
==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