为什么这样做:从std :: string函数返回C字符串文字并调用c_str()

时间:2014-01-02 12:38:37

标签: c++

我们最近在大学进行了一次演讲,我们的教授告诉我们在使用不同语言编程时需要注意的不同事项。 以下是C ++中的一个示例:

std::string myFunction()
{
    return "it's me!!";
}

int main(int argc, const char * argv[])
{
    const char* tempString = myFunction().c_str();

    char myNewString[100] = "Who is it?? - ";
    strcat(myNewString, tempString);
    printf("The string: %s", myNewString);

    return 0;
}

为什么会失败的想法是return "it's me!!"使用char []隐式调用std :: string构造函数。该字符串从函数返回,函数c_str()返回指向std::string数据的指针。

由于函数返回的字符串未在任何地方引用,因此应立即取消分配。这就是理论。

然而,让这段代码运行没有问题。 很想知道你的想法。 谢谢!

9 个答案:

答案 0 :(得分:45)

您的分析是正确的。你拥有的是未定义的行为。这意味着几乎任何事情都可能发生。在您的情况下,用于字符串的内存似乎虽然取消分配,但在访问时仍保留原始内容。这通常是因为操作系统没有清除解除分配的内存。它只是标记为可供将来使用。这不是C ++语言必须处理的事情:它实际上是一个OS实现细节。就C ++而言,适用所有“未定义的行为”都适用。

答案 1 :(得分:5)

我想解除分配并不意味着内存清理或归零。显然,这可能导致其他情况下的段错误。

答案 2 :(得分:3)

正如其他人所提到的,根据C ++标准,这是未定义的行为。

这个“工作”的原因是因为内存已经被返回给堆管理器,堆管理器保留它以供以后重用。内存已已返回操作系统,因此仍属于该进程。这就是访问释放内存不会导致分段错误的原因。然而问题仍然是,现在程序的两个部分(您的代码和堆管理器或新所有者)正在访问他们认为唯一属于它们的内存。这迟早会破坏事物。

答案 3 :(得分:3)

我认为原因是堆栈内存没有被重写,所以它可以获取原始数据。我创建了一个测试函数并在strcat之前调用它。

std::string myFunction()
{
    return "it's me!!";
}


void test()
{
    std::string str = "this is my class";
    std::string hi = "hahahahahaha";

    return;
}

int main(int argc, const char * argv[])
{
    const char* tempString = myFunction().c_str();


    test();
    char myNewString[100] = "Who is it?? - ";
    strcat(myNewString, tempString);
    printf("The string: %s\n", myNewString);

    return 0;
}

得到结果:

The string: Who is it?? - hahahahahaha

这证明了我的想法。

答案 4 :(得分:1)

字符串被释放的事实并不一定意味着内存不再可访问。只要你不做任何可能覆盖它的事情,内存仍然可用。

答案 5 :(得分:1)

如上所述 - 这是不可预测的行为。它对我不起作用(在Debug配置中)。 在赋值给tempString后立即调用std :: string析构函数 - 当使用临时字符串对象的表达式完成时。 让tempString指向已释放的内存(在你的情况下仍然包含“它就是我!!”文字)。

答案 6 :(得分:1)

你无法通过巧合获得结果来得出结论没有问题。

还有其他方法可以检测'问题':

  • 静态分析。
  • Valgrind将捕获错误,向您显示违规操作(尝试从已释放的区域-by strcat进行复制)以及导致释放的释放。

Invalid read of size 1

   at 0x40265BD: strcat (mc_replace_strmem.c:262)
   by 0x80A5BDB: main() (valgrind_sample_for_so.cpp:20)
   [...]
Address 0x5be236d is 13 bytes inside a block of size 55 free'd
   at 0x4024B46: operator delete(void*) (vg_replace_malloc.c:480)
   by 0x563E6BC: std::string::_Rep::_M_destroy(std::allocator<char> const&) (in /usr/lib/libstdc++.so.6.0.13)
   by 0x80A5C18: main() (basic_string.h:236)
   [...]

at 0x40265BD: strcat (mc_replace_strmem.c:262) by 0x80A5BDB: main() (valgrind_sample_for_so.cpp:20) [...] Address 0x5be236d is 13 bytes inside a block of size 55 free'd at 0x4024B46: operator delete(void*) (vg_replace_malloc.c:480) by 0x563E6BC: std::string::_Rep::_M_destroy(std::allocator<char> const&) (in /usr/lib/libstdc++.so.6.0.13) by 0x80A5C18: main() (basic_string.h:236) [...]

  • 唯一的方法是证明程序正确。但是对于过程语言来说真的很难,C ++会让它变得更难。

答案 7 :(得分:-1)

实际上,字符串文字具有静态存储持续时间。它们包含在可执行文件本身内。它们不在堆栈中,也不是动态分配的。在通常的情况下,这将指向无效的内存并且是未定义的行为是正确的,但是对于字符串,内存在静态存储中,因此它总是有效的。

答案 8 :(得分:-1)

除非我遗漏了什么,否则我认为这是一个范围问题。 myFunction()返回一个std :: string。字符串对象不直接分配给变量。但它一直在范围内,直到main()结束。因此,tempString将指向内存中完全有效且可用的空间,直到main()代码块结束,此时tempString也将超出范围。