我们正与朋友们就代码进行热烈的讨论:
#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)
答案 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
:指针p
,p + 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;
)而不是绑定到临时对象的方法的结果时