2016-03-14 18 views
8

Tôi có đoạn mã sau:Tại sao gọi std :: string.c_str() trên một hàm trả về một chuỗi không hoạt động?

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 
} 

Những gì tôi nghĩ sẽ xảy ra là getString() sẽ trả về một bản sao của str (getString() lợi nhuận theo giá trị); do đó, bản sao của str sẽ giữ nguyên trạng thái "còn sống" trong main() cho đến khi trả về main(). Điều này sẽ làm cho cStr trỏ đến vị trí bộ nhớ hợp lệ: số char[] hoặc char* (hoặc bất kỳ) nào của bản sao của str được trả lại bởi getString(), vẫn còn trong main().

Tuy nhiên, điều này rõ ràng không phải là trường hợp, vì chương trình sẽ xuất ra rác. Vì vậy, câu hỏi đặt ra là khi nào str bị hủy và tại sao?

+0

Mã của bạn hoạt động tốt đối với tôi. –

+13

@PriyanshGoel Hành vi không xác định đôi khi giống như vậy. –

+0

Tôi không thể hiểu lý do hành vi không xác định. –

Trả lời

18

getString() sẽ trả về bản sao str (getString() trả về theo giá trị);

Đúng vậy.

do đó, bản sao của str sẽ giữ nguyên "còn sống" trong main() cho đến khi trả lại main().

Không, sao lại là tạm thời std::string, mà sẽ bị phá hủy vào cuối báo cáo kết quả, trong đó nó được tạo ra, ví dụ: trước std::cout << cStr << std::endl;. Sau đó, cStr trở nên lơ lửng, dereference trên nó dẫn đến UB, bất cứ điều gì là có thể.

Bạn có thể sao chép tạm thời trở lại biến được đặt tên hoặc liên kết nó với tham chiếu giá trị tham chiếu hoặc tham chiếu giá trị tham chiếu const (thời gian tạm thời sẽ được kéo dài cho đến khi tham chiếu nằm ngoài phạm vi). Chẳng hạn như:

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 

Dưới đây là một lời giải thích từ 10.4.10 Đối tượng tạm thời [class.temp]] [The.C++ Programming.Language.Special.Edition.]:

Trừ khi ràng buộc với một tham chiếu hoặc được sử dụng để khởi tạo một đối tượng được đặt tên, một đối tượng tạm thời bị hủy ở cuối biểu thức đầy đủ trong mà nó đã được tạo. Biểu thức đầy đủ là biểu thức là không phải là biểu thức phụ của một số biểu thức khác.

Lớp chuỗi chuẩn có hàm thành viên c_str() trả về một mảng ký tự kiểu C, không bị chấm dứt (§3.5.1, §20.4.1). Ngoài ra, toán tử + được định nghĩa có nghĩa là nối chuỗi. Đây là những cơ sở rất hữu ích cho chuỗi. Tuy nhiên, kết hợp chúng có thể gây ra các vấn đề tối nghĩa. Ví dụ:

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 
    } 

} 

Có thể phản ứng đầu tiên của bạn là "nhưng không làm điều đó" và tôi đồng ý. Tuy nhiên, mã như vậy không được viết, do đó, nó là giá trị biết làm thế nào nó là giải thích.

Đối tượng tạm thời của chuỗi lớp được tạo để giữ s1 + s2. Tiếp theo, một con trỏ tới một chuỗi kiểu C được trích xuất từ ​​đối tượng đó. Sau đó - ở cuối biểu thức - đối tượng tạm thời bị xóa. Bây giờ, chuỗi phân cách kiểu C được phân bổ ở đâu? Có lẽ là một phần của đối tượng tạm thời giữ s1 + s2 và lưu trữ đó không được bảo đảm để tồn tại sau khi tạm thời bị hủy. Do đó, cs điểm để lưu trữ được phân phối lại. Các hoạt động đầu ra cout < < cs có thể hoạt động như mong đợi, nhưng đó sẽ là may mắn tuyệt đối. Trình biên dịch có thể phát hiện và cảnh báo chống lại nhiều biến thể của vấn đề này.

+0

Nhưng lưu tạm thời std :: string trong một biến như: std :: string str = getString(); sẽ không phá hủy bản sao? tại sao không? là vì = sử dụng hàm tạo sao chép? –

+0

Tôi xin lỗi nhưng tôi không hiểu điều này. Tôi đồng ý rằng str sẽ bị phá hủy nhưng sau đó tôi đã lưu trữ giá trị của string.c_str trong một biến khác. Nó sẽ in hello. –

+0

@FakeJake Có, biến được đặt tên 'str' sẽ được sao chép khởi tạo từ biến tạm thời, (mặc dù bản sao sẽ bị bỏ qua thường bởi RVO), sau đó' str' sẽ hợp lệ cho đến khi 'main()' kết thúc, vì vậy nó sẽ được an toàn để sử dụng 'str.c_str()' trong 'main()'. – songyuanyao

4

Vấn đề ở đây là bạn đang trả lại biến tạm thời và trên biến tạm thời mà bạn đang làm chức năng c_str.

"c_str() chức năng Trả về một con trỏ đến một mảng chứa một null-terminated chuỗi các ký tự (ví dụ, một C-string) đại diện cho các giá trị hiện tại của đối tượng string ( [http://www.cplusplus.com/reference/string/string/c_str/][1]).

Trong trường hợp này con trỏ của bạn được trỏ đến vị trí nhớ mà bây giờ không có mặt.

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 } 

giải pháp là để sao chép ob tạm thời của bạn ject đến vị trí bộ nhớ đúng (bằng cách tạo bản sao cục bộ) và sau đó sử dụng c_str trên đối tượng đó.

1

Như đã đề cập bởi những người khác, bạn đang sử dụng con trỏ tạm thời sau khi đã bị xóa - đây là ví dụ cổ điển về số sau khi sử dụng miễn phí.

Những gì tôi có thể thêm vào câu trả lời của người khác là bạn có thể dễ dàng phát hiện việc sử dụng như vậy với gcc's hoặc clang's vệ sinh địa chỉ.

Ví dụ:

#include <string> 
#include <iostream> 

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

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

khử trùng đầu ra:

================================================================= 
==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 
Các vấn đề liên quan