使用临时寿命差异

时间:2016-01-04 14:04:52

标签: c++

我们正与朋友们就代码进行热烈的讨论:

#include <iostream>
#include <string>

using namespace std;

string getString() {
    return string("Hello, world!");
}

int main() {
    char const * str = getString().c_str();
    std::cout << str << "\n";
    return 0;
}

此代码在g ++,clang和vc ++上生成不同的输出:

g++clang输出相同:

  

你好,世界!

但是vc++不输出任何内容(或只是空格):

  

什么行为是正确的?根据临时生活,这可能是标准的变化吗?

据我所知,通过阅读clang++的IR,我的工作原理如下:

store `getString()`'s return value in %1
std::cout << %1.c_str() << "\n";
destruct %1

就个人而言,我认为gcc也是这样的(我用rvo / move verbosity测试它(自定义ctors和dtors打印到std::cout)。为什么vc ++以其他方式工作?< / p>

clang = Apple LLVM版本6.1.0(clang-602.0.53)(基于LLVM 3.6.0svn)

g ++ = gcc version 4.9.2(Debian 4.9.2-10)

3 个答案:

答案 0 :(得分:8)

您的程序有未定义的行为!你是&#34;打印&#34;一个悬垂的指针。

临时字符串getString()的结果不会超过const char*声明;因此,也没有在该临时性上调用c_str()的结果。

所以两个编译器都是&#34;正确&#34 ;;你和你的朋友是错的。

这就是为什么我们不会存储std::string::c_str()的结果,除非我们确实需要。

答案 1 :(得分:8)

两者都是正确的,未定义的行为未定义。

char const * str = getString().c_str();

getString()返回一个临时的,它将在包含它的完整表达式的末尾被销毁。因此,在该行完成后,str是一个无效的指针,并且试图检查它将使你陷入未定义行为的土地。

根据要求提供一些标准报价(来自N4140):

  

[class.temporary]/3:临时对象作为评估全表达式的最后一步被销毁,该表达式(词法上)包含创建它们的点。

basic_string::c_str的指定如下:

  

[string.accessors]/1:指针pp + i == &operator[](i)中的每个i [0,size()]

由于字符串的内容是连续存储的([string.require]/4),这实际上意味着&#34;返回一个指向缓冲区开头的指针&#34;。

显然,当std::string被破坏时,它将回收任何已分配的内存,使该指针无效(如果你的朋友不相信,他们还有其他问题)。

答案 2 :(得分:5)

这是未定义的行为,因此任何事情都可能发生(包括“正确地”打印字符串)。

除非程序实际上是在付费客户的计算机上运行,​​或者如果它在大屏幕上显示在大量观众面前,否则无论如何都会让事情“正常”发生在UB上; - )

问题是你在使用指针之前将const char *指向一个被销毁的临时对象。

请注意,这与 的情况相同:

const std::string& str = getString(); // Returns a temporary
std::cout << str << "\n";

因为在这种情况下,有一个非常具体的规则,关于绑定到C ++标准中临时值的引用。在这种情况下,临时的生命周期将延长,直到引用str也被销毁。该规则仅适用于引用,并且仅当直接绑定到临时或临时的子对象(如const std::string& s = getObj().s;)而不是绑定到临时对象的方法的结果时