为什么在返回字符串的函数上调用std :: string.c_str()不起作用?

时间:2016-03-14 06:15:14

标签: c++ string object-lifetime

我有以下代码:

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
}

我认为会发生getString()会返回str副本getString()按值返回);因此,str的副本将保持&#34;活着&#34;在main()中,直到main()返回。这会使cStr指向有效的内存位置:char[]返回的char*副本的基础strgetString()(或其他),保留在main()

然而,显然情况并非如此,因为程序会输出垃圾。所以,问题是,str何时被破坏,为什么?

3 个答案:

答案 0 :(得分:19)

  

getString()会返回str的副本(getString()按值返回);

没错。

  

因此,str的副本会在main()中保持“活着”,直到main()返回。

不,返回的副本是临时的std::string,它将在创建它的语句的末尾销毁,即在std::cout << cStr << std::endl;之前销毁。然后cStr变得悬空,取消引用就会导致 UB ,一切皆有可能。

您可以将返回的临时值复制到命名变量,或将其绑定到const左值引用或右值引用(临时值的生命周期将延长,直到引用超出范围)。如:

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

以下是 [The.C ++ .Programming.Language.Special.Edition] 10.4.10临时对象[class.temp]] 的解释:

  

除非绑定到引用或用于初始化命名对象,否则a   临时对象在完整表达式的末尾被销毁   它是创建的。完整表达式是一个表达式   不是其他表达式的子表达式。

     

标准字符串类有一个成员函数c_str()即可   返回一个C风格的,以零结尾的字符数组(§3.5.1,§20.4.1)。此外,operator +被定义为表示字符串连接。   这些是非常有用的字符串工具。但是,它们可能会导致模糊的问题。   例如:

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
    }

}
     

可能你的第一反应是“但不要这样做”,我同意。   但是,这样的代码确实写了,所以值得知道它是如何编写的   解释

     

创建类字符串的临时对象以保存s1 + s2。   接下来,从该对象中提取指向C样式字符串的指针。然后    - 在表达式的末尾 - 删除临时对象。现在,   分配了C风格的字符串在哪里?可能作为一部分   临时对象持有s1 + s2,并且不保证该存储   在临时被摧毁之后存在。因此,cs指出   解除分配存储空间。输出操作cout&lt;&lt;可能会有用   正如所料,但那将是纯粹的运气。编译器可以检测和   警告这个问题的许多变种。

答案 1 :(得分:4)

这里的问题是你要返回一个临时变量 那个临时变量你正在做c_str函数。

  

&#34; c_str()函数返回指向包含以null结尾的数组的指针   表示当前的字符序列(即,C字符串)   字符串对象的值(   [http://www.cplusplus.com/reference/string/string/c_str/][1])。

在这种情况下,指针指向现在不存在的内存位置。

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 }

解决方法是将临时对象正确复制到内存位置(通过创建本地副本),然后在该对象上使用c_str。

答案 2 :(得分:1)

正如其他人所提到的那样,你在删除它之后使用了指向临时的指针 - 这是免费使用后的堆的典型例子。

我能为他人添加什么&#39;答案是,您可以使用gcc'sclang's地址清理程序轻松检测此类使用情况。

示例:

#include <string>
#include <iostream>

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

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

消毒剂输出:

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